Copilot commented on code in PR #103:
URL: https://github.com/apache/paimon-cpp/pull/103#discussion_r3456626454


##########
src/paimon/core/operation/file_store_commit_impl_test.cpp:
##########
@@ -0,0 +1,1666 @@
+/*
+ * 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.
+ */
+
+#include "paimon/core/operation/file_store_commit_impl.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <filesystem>
+#include <iostream>
+#include <set>
+#include <utility>
+
+#include "arrow/c/abi.h"
+#include "arrow/c/bridge.h"
+#include "arrow/status.h"
+#include "arrow/type.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "paimon/catalog/catalog.h"
+#include "paimon/catalog/identifier.h"
+#include "paimon/commit_context.h"
+#include "paimon/commit_message.h"
+#include "paimon/common/data/binary_row.h"
+#include "paimon/common/data/binary_row_writer.h"
+#include "paimon/common/factories/io_hook.h"
+#include "paimon/common/utils/path_util.h"
+#include "paimon/common/utils/scope_guard.h"
+#include "paimon/core/catalog/commit_table_request.h"
+#include "paimon/core/io/data_file_meta.h"
+#include "paimon/core/manifest/file_kind.h"
+#include "paimon/core/manifest/index_manifest_entry.h"
+#include "paimon/core/manifest/manifest_committable.h"
+#include "paimon/core/manifest/manifest_entry.h"
+#include "paimon/core/manifest/manifest_file_meta.h"
+#include "paimon/core/manifest/manifest_list.h"
+#include "paimon/core/operation/metrics/commit_metrics.h"
+#include "paimon/core/partition/partition_statistics.h"
+#include "paimon/core/stats/simple_stats.h"
+#include "paimon/core/table/sink/commit_message_impl.h"
+#include "paimon/core/utils/file_utils.h"
+#include "paimon/core/utils/snapshot_manager.h"
+#include "paimon/data/timestamp.h"
+#include "paimon/defs.h"
+#include "paimon/factories/factory_creator.h"
+#include "paimon/fs/file_system.h"
+#include "paimon/fs/local/local_file_system.h"
+#include "paimon/fs/local/local_file_system_factory.h"
+#include "paimon/memory/memory_pool.h"
+#include "paimon/metrics.h"
+#include "paimon/testing/utils/binary_row_generator.h"
+#include "paimon/testing/utils/io_exception_helper.h"
+#include "paimon/testing/utils/testharness.h"
+#include "paimon/testing/utils/timezone_guard.h"
+
+namespace paimon::test {
+class GmockFileSystem : public LocalFileSystem {
+ public:
+    MOCK_METHOD(Status, ReadFile, (const std::string& path, std::string* 
content), (override));
+    MOCK_METHOD(Status, ListDir,
+                (const std::string& directory,
+                 std::vector<std::unique_ptr<BasicFileStatus>>* 
file_status_list),
+                (const, override));
+    MOCK_METHOD(Status, AtomicStore, (const std::string& path, const 
std::string& content),
+                (override));
+};
+
+class GmockFileSystemFactory : public LocalFileSystemFactory {
+ public:
+    const char* Identifier() const override {
+        return "gmock_fs";
+    }
+
+    Result<std::unique_ptr<FileSystem>> Create(
+        const std::string& path, const std::map<std::string, std::string>& 
options) const override {
+        auto fs = std::make_unique<testing::NiceMock<GmockFileSystem>>();
+        auto fs_ptr = fs.get();
+        using ::testing::A;
+        using ::testing::Invoke;
+
+        ON_CALL(*fs, ListDir(A<const std::string&>(),
+                             
A<std::vector<std::unique_ptr<BasicFileStatus>>*>()))
+            .WillByDefault(
+                Invoke([fs_ptr](const std::string& directory,
+                                std::vector<std::unique_ptr<BasicFileStatus>>* 
file_status_list) {
+                    return fs_ptr->LocalFileSystem::ListDir(directory, 
file_status_list);
+                }));
+
+        ON_CALL(*fs, ReadFile(A<const std::string&>(), A<std::string*>()))
+            .WillByDefault(Invoke([fs_ptr](const std::string& path, 
std::string* content) {
+                return fs_ptr->FileSystem::ReadFile(path, content);
+            }));
+
+        ON_CALL(*fs, AtomicStore(A<const std::string&>(), A<const 
std::string&>()))
+            .WillByDefault(Invoke([fs_ptr](const std::string& path, const 
std::string& content) {
+                return fs_ptr->FileSystem::AtomicStore(path, content);
+            }));
+
+        return fs;
+    }
+};
+
+class FileStoreCommitImplTest : public testing::Test {
+ public:
+    void SetUp() override {
+        auto factory_creator = paimon::FactoryCreator::GetInstance();
+        factory_creator->Register("gmock_fs", (new GmockFileSystemFactory));
+        dir_ = UniqueTestDirectory::Create();
+        ASSERT_TRUE(dir_);
+        test_root_ = dir_->Str();
+        file_system_ = std::make_shared<LocalFileSystem>();
+        fields_ = {arrow::field("f0", arrow::utf8()), arrow::field("f1", 
arrow::int32()),
+                   arrow::field("f2", arrow::int32()), arrow::field("f3", 
arrow::float64())};
+        ASSERT_OK_AND_ASSIGN(auto catalog, Catalog::Create(test_root_, {}));
+        ASSERT_OK(catalog->CreateDatabase("foo", {}, 
/*ignore_if_exists=*/false));
+        arrow::Schema typed_schema(fields_);
+        ::ArrowSchema schema;
+        ASSERT_TRUE(arrow::ExportSchema(typed_schema, &schema).ok());
+        ASSERT_OK(catalog->CreateTable(Identifier("foo", "bar"), &schema,
+                                       /*partition_keys=*/{"f1"},
+                                       /*primary_keys=*/{}, {},
+                                       /*ignore_if_exists=*/false));
+        table_path_ = PathUtil::JoinPath(test_root_, "foo.db/bar");
+    }
+    void TearDown() override {
+        auto factory_creator = paimon::FactoryCreator::GetInstance();
+        factory_creator->TEST_Unregister("gmock_fs");
+    }
+
+    void Print(const std::vector<ManifestEntry>& entries) {
+        for (const auto& entry : entries) {
+            std::cout << entry.FileName() << " ";
+        }
+        std::cout << std::endl;
+    }
+
+    ManifestEntry CreateManifestEntry(const std::string& file_name, const 
FileKind& kind) const {
+        int32_t arity = 1;
+        BinaryRow row(arity);
+        BinaryRowWriter writer(&row, 20, GetDefaultPool().get());
+        writer.WriteInt(0, 10);
+        writer.Complete();
+        return CreateManifestEntry(file_name, row, kind);
+    }
+
+    ManifestEntry CreateManifestEntry(const std::string& file_name, const 
BinaryRow& partition,
+                                      const FileKind& kind) const {
+        auto data_file_meta = std::make_shared<DataFileMeta>(
+            file_name, 1024, 8, DataFileMeta::EmptyMinKey(), 
DataFileMeta::EmptyMaxKey(),
+            SimpleStats::EmptyStats(), SimpleStats::EmptyStats(), 
/*min_seq_no=*/16,
+            /*max_seq_no=*/32,
+            /*schema_id=*/1, /*level=*/2,
+            /*extra_files=*/std::vector<std::optional<std::string>>(),
+            /*creation_time=*/Timestamp(0, 0),
+            /*delete_row_count=*/3,
+            /*embedded_index=*/nullptr, /*file_source=*/std::nullopt,
+            /*external_path=*/std::nullopt,
+            /*value_stats_cols=*/std::nullopt, /*first_row_id=*/std::nullopt,
+            /*write_cols=*/std::nullopt);
+        return ManifestEntry(kind, partition, 0, 2, data_file_meta);
+    }
+
+    ManifestEntry CreateManifestEntryWithNoPartition(const std::string& 
file_name,
+                                                     const FileKind& kind) 
const {
+        auto data_file_meta = std::make_shared<DataFileMeta>(
+            file_name, 1024, 8, DataFileMeta::EmptyMinKey(), 
DataFileMeta::EmptyMaxKey(),
+            SimpleStats::EmptyStats(), SimpleStats::EmptyStats(), 
/*min_seq_no=*/16,
+            /*max_seq_no=*/32,
+            /*schema_id=*/1, /*level=*/2,
+            /*extra_files=*/std::vector<std::optional<std::string>>(),
+            /*creation_time=*/Timestamp(0, 0),
+            /*delete_row_count=*/3,
+            /*embedded_index=*/nullptr, /*file_source=*/std::nullopt,
+            /*external_path=*/std::nullopt,
+            /*value_stats_cols=*/std::nullopt, /*first_row_id=*/std::nullopt,
+            /*write_cols=*/std::nullopt);
+        return ManifestEntry(kind, BinaryRow::EmptyRow(), 0, 2, 
data_file_meta);
+    }
+
+    std::set<std::string> CollectFileNames(const std::vector<ManifestEntry>& 
entries) {
+        std::set<std::string> result;
+        for (const auto& entry : entries) {
+            result.insert(entry.FileName());
+        }
+        return result;
+    }
+
+    bool IsStringInSet(const std::set<std::string>& strSet, const std::string& 
target) {
+        return strSet.find(target) != strSet.end();
+    }
+
+    bool HitIOHook(const Status& status) {
+        if (status.ToString().find("io hook triggered io error") != 
std::string::npos) {
+            return true;
+        }
+        return false;
+    }
+
+    bool HitIOHookInCommitHint(const Status& status) {
+        if (status.ToString().find("io hook triggered io error at position") 
!= std::string::npos &&
+            (status.ToString().find("snapshot/LATEST") != std::string::npos ||
+             status.ToString().find("snapshot/EARLIEST") != 
std::string::npos)) {
+            return true;
+        }
+        return false;
+    }
+
+ private:
+    Status PrepareFakeFiles(const std::vector<std::string>& files) {
+        for (const auto& file : files) {
+            auto path = PathUtil::JoinPath(table_path_, file);
+            PAIMON_RETURN_NOT_OK(
+                file_system_->WriteFile(path, /*content=*/"", 
/*overwrite=*/false));
+        }
+        return Status::OK();
+    }
+
+    bool IsEqualMsgs(const std::vector<std::shared_ptr<CommitMessage>>& 
expected_msgs,
+                     const std::vector<std::shared_ptr<CommitMessage>>& 
actual_msgs) {
+        if (expected_msgs.size() != actual_msgs.size()) {
+            return false;
+        }
+        for (size_t i = 0; i < expected_msgs.size(); i++) {
+            auto actual = 
std::dynamic_pointer_cast<CommitMessageImpl>(actual_msgs[i]);
+            auto expected = 
std::dynamic_pointer_cast<CommitMessageImpl>(expected_msgs[i]);
+            if (*actual == *expected) {
+                continue;
+            } else {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    std::vector<std::shared_ptr<CommitMessage>> GetCommitMessages(const 
std::string& path,
+                                                                  int32_t 
version) const {
+        auto file_system = GetFileSystem();
+        EXPECT_OK_AND_ASSIGN(std::unique_ptr<FileStatus> file, 
file_system->GetFileStatus(path));
+        std::vector<uint8_t> buffer(file->GetLen(), 0);
+
+        EXPECT_OK_AND_ASSIGN(std::unique_ptr<InputStream> in_stream, 
file_system->Open(path));
+        EXPECT_TRUE(in_stream);
+        EXPECT_OK_AND_ASSIGN([[maybe_unused]] int32_t length,
+                             
in_stream->Read(reinterpret_cast<char*>(buffer.data()), buffer.size()))
+        EXPECT_OK(in_stream->Close());

Review Comment:
   Missing trailing semicolon after EXPECT_OK_AND_ASSIGN(...) causes a compile 
error (the macro expands to statements and requires an explicit ';' at the call 
site).



##########
src/paimon/core/operation/file_store_commit_impl_test.cpp:
##########
@@ -0,0 +1,1666 @@
+/*
+ * 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.
+ */
+
+#include "paimon/core/operation/file_store_commit_impl.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <filesystem>
+#include <iostream>
+#include <set>
+#include <utility>
+
+#include "arrow/c/abi.h"
+#include "arrow/c/bridge.h"
+#include "arrow/status.h"
+#include "arrow/type.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "paimon/catalog/catalog.h"
+#include "paimon/catalog/identifier.h"
+#include "paimon/commit_context.h"
+#include "paimon/commit_message.h"
+#include "paimon/common/data/binary_row.h"
+#include "paimon/common/data/binary_row_writer.h"
+#include "paimon/common/factories/io_hook.h"
+#include "paimon/common/utils/path_util.h"
+#include "paimon/common/utils/scope_guard.h"
+#include "paimon/core/catalog/commit_table_request.h"
+#include "paimon/core/io/data_file_meta.h"
+#include "paimon/core/manifest/file_kind.h"
+#include "paimon/core/manifest/index_manifest_entry.h"
+#include "paimon/core/manifest/manifest_committable.h"
+#include "paimon/core/manifest/manifest_entry.h"
+#include "paimon/core/manifest/manifest_file_meta.h"
+#include "paimon/core/manifest/manifest_list.h"
+#include "paimon/core/operation/metrics/commit_metrics.h"
+#include "paimon/core/partition/partition_statistics.h"
+#include "paimon/core/stats/simple_stats.h"
+#include "paimon/core/table/sink/commit_message_impl.h"
+#include "paimon/core/utils/file_utils.h"
+#include "paimon/core/utils/snapshot_manager.h"
+#include "paimon/data/timestamp.h"
+#include "paimon/defs.h"
+#include "paimon/factories/factory_creator.h"
+#include "paimon/fs/file_system.h"
+#include "paimon/fs/local/local_file_system.h"
+#include "paimon/fs/local/local_file_system_factory.h"
+#include "paimon/memory/memory_pool.h"
+#include "paimon/metrics.h"
+#include "paimon/testing/utils/binary_row_generator.h"
+#include "paimon/testing/utils/io_exception_helper.h"
+#include "paimon/testing/utils/testharness.h"
+#include "paimon/testing/utils/timezone_guard.h"
+
+namespace paimon::test {
+class GmockFileSystem : public LocalFileSystem {
+ public:
+    MOCK_METHOD(Status, ReadFile, (const std::string& path, std::string* 
content), (override));
+    MOCK_METHOD(Status, ListDir,
+                (const std::string& directory,
+                 std::vector<std::unique_ptr<BasicFileStatus>>* 
file_status_list),
+                (const, override));
+    MOCK_METHOD(Status, AtomicStore, (const std::string& path, const 
std::string& content),
+                (override));
+};
+
+class GmockFileSystemFactory : public LocalFileSystemFactory {
+ public:
+    const char* Identifier() const override {
+        return "gmock_fs";
+    }
+
+    Result<std::unique_ptr<FileSystem>> Create(
+        const std::string& path, const std::map<std::string, std::string>& 
options) const override {
+        auto fs = std::make_unique<testing::NiceMock<GmockFileSystem>>();
+        auto fs_ptr = fs.get();
+        using ::testing::A;
+        using ::testing::Invoke;
+
+        ON_CALL(*fs, ListDir(A<const std::string&>(),
+                             
A<std::vector<std::unique_ptr<BasicFileStatus>>*>()))
+            .WillByDefault(
+                Invoke([fs_ptr](const std::string& directory,
+                                std::vector<std::unique_ptr<BasicFileStatus>>* 
file_status_list) {
+                    return fs_ptr->LocalFileSystem::ListDir(directory, 
file_status_list);
+                }));
+
+        ON_CALL(*fs, ReadFile(A<const std::string&>(), A<std::string*>()))
+            .WillByDefault(Invoke([fs_ptr](const std::string& path, 
std::string* content) {
+                return fs_ptr->FileSystem::ReadFile(path, content);
+            }));
+
+        ON_CALL(*fs, AtomicStore(A<const std::string&>(), A<const 
std::string&>()))
+            .WillByDefault(Invoke([fs_ptr](const std::string& path, const 
std::string& content) {
+                return fs_ptr->FileSystem::AtomicStore(path, content);
+            }));
+
+        return fs;
+    }
+};
+
+class FileStoreCommitImplTest : public testing::Test {
+ public:
+    void SetUp() override {
+        auto factory_creator = paimon::FactoryCreator::GetInstance();
+        factory_creator->Register("gmock_fs", (new GmockFileSystemFactory));
+        dir_ = UniqueTestDirectory::Create();
+        ASSERT_TRUE(dir_);
+        test_root_ = dir_->Str();
+        file_system_ = std::make_shared<LocalFileSystem>();
+        fields_ = {arrow::field("f0", arrow::utf8()), arrow::field("f1", 
arrow::int32()),
+                   arrow::field("f2", arrow::int32()), arrow::field("f3", 
arrow::float64())};
+        ASSERT_OK_AND_ASSIGN(auto catalog, Catalog::Create(test_root_, {}));
+        ASSERT_OK(catalog->CreateDatabase("foo", {}, 
/*ignore_if_exists=*/false));
+        arrow::Schema typed_schema(fields_);
+        ::ArrowSchema schema;
+        ASSERT_TRUE(arrow::ExportSchema(typed_schema, &schema).ok());
+        ASSERT_OK(catalog->CreateTable(Identifier("foo", "bar"), &schema,
+                                       /*partition_keys=*/{"f1"},
+                                       /*primary_keys=*/{}, {},
+                                       /*ignore_if_exists=*/false));
+        table_path_ = PathUtil::JoinPath(test_root_, "foo.db/bar");
+    }
+    void TearDown() override {
+        auto factory_creator = paimon::FactoryCreator::GetInstance();
+        factory_creator->TEST_Unregister("gmock_fs");
+    }
+
+    void Print(const std::vector<ManifestEntry>& entries) {
+        for (const auto& entry : entries) {
+            std::cout << entry.FileName() << " ";
+        }
+        std::cout << std::endl;
+    }
+
+    ManifestEntry CreateManifestEntry(const std::string& file_name, const 
FileKind& kind) const {
+        int32_t arity = 1;
+        BinaryRow row(arity);
+        BinaryRowWriter writer(&row, 20, GetDefaultPool().get());
+        writer.WriteInt(0, 10);
+        writer.Complete();
+        return CreateManifestEntry(file_name, row, kind);
+    }
+
+    ManifestEntry CreateManifestEntry(const std::string& file_name, const 
BinaryRow& partition,
+                                      const FileKind& kind) const {
+        auto data_file_meta = std::make_shared<DataFileMeta>(
+            file_name, 1024, 8, DataFileMeta::EmptyMinKey(), 
DataFileMeta::EmptyMaxKey(),
+            SimpleStats::EmptyStats(), SimpleStats::EmptyStats(), 
/*min_seq_no=*/16,
+            /*max_seq_no=*/32,
+            /*schema_id=*/1, /*level=*/2,
+            /*extra_files=*/std::vector<std::optional<std::string>>(),
+            /*creation_time=*/Timestamp(0, 0),
+            /*delete_row_count=*/3,
+            /*embedded_index=*/nullptr, /*file_source=*/std::nullopt,
+            /*external_path=*/std::nullopt,
+            /*value_stats_cols=*/std::nullopt, /*first_row_id=*/std::nullopt,
+            /*write_cols=*/std::nullopt);
+        return ManifestEntry(kind, partition, 0, 2, data_file_meta);
+    }
+
+    ManifestEntry CreateManifestEntryWithNoPartition(const std::string& 
file_name,
+                                                     const FileKind& kind) 
const {
+        auto data_file_meta = std::make_shared<DataFileMeta>(
+            file_name, 1024, 8, DataFileMeta::EmptyMinKey(), 
DataFileMeta::EmptyMaxKey(),
+            SimpleStats::EmptyStats(), SimpleStats::EmptyStats(), 
/*min_seq_no=*/16,
+            /*max_seq_no=*/32,
+            /*schema_id=*/1, /*level=*/2,
+            /*extra_files=*/std::vector<std::optional<std::string>>(),
+            /*creation_time=*/Timestamp(0, 0),
+            /*delete_row_count=*/3,
+            /*embedded_index=*/nullptr, /*file_source=*/std::nullopt,
+            /*external_path=*/std::nullopt,
+            /*value_stats_cols=*/std::nullopt, /*first_row_id=*/std::nullopt,
+            /*write_cols=*/std::nullopt);
+        return ManifestEntry(kind, BinaryRow::EmptyRow(), 0, 2, 
data_file_meta);
+    }
+
+    std::set<std::string> CollectFileNames(const std::vector<ManifestEntry>& 
entries) {
+        std::set<std::string> result;
+        for (const auto& entry : entries) {
+            result.insert(entry.FileName());
+        }
+        return result;
+    }
+
+    bool IsStringInSet(const std::set<std::string>& strSet, const std::string& 
target) {
+        return strSet.find(target) != strSet.end();
+    }
+
+    bool HitIOHook(const Status& status) {
+        if (status.ToString().find("io hook triggered io error") != 
std::string::npos) {
+            return true;
+        }
+        return false;
+    }
+
+    bool HitIOHookInCommitHint(const Status& status) {
+        if (status.ToString().find("io hook triggered io error at position") 
!= std::string::npos &&
+            (status.ToString().find("snapshot/LATEST") != std::string::npos ||
+             status.ToString().find("snapshot/EARLIEST") != 
std::string::npos)) {
+            return true;
+        }
+        return false;
+    }
+
+ private:
+    Status PrepareFakeFiles(const std::vector<std::string>& files) {
+        for (const auto& file : files) {
+            auto path = PathUtil::JoinPath(table_path_, file);
+            PAIMON_RETURN_NOT_OK(
+                file_system_->WriteFile(path, /*content=*/"", 
/*overwrite=*/false));
+        }
+        return Status::OK();
+    }
+
+    bool IsEqualMsgs(const std::vector<std::shared_ptr<CommitMessage>>& 
expected_msgs,
+                     const std::vector<std::shared_ptr<CommitMessage>>& 
actual_msgs) {
+        if (expected_msgs.size() != actual_msgs.size()) {
+            return false;
+        }
+        for (size_t i = 0; i < expected_msgs.size(); i++) {
+            auto actual = 
std::dynamic_pointer_cast<CommitMessageImpl>(actual_msgs[i]);
+            auto expected = 
std::dynamic_pointer_cast<CommitMessageImpl>(expected_msgs[i]);
+            if (*actual == *expected) {
+                continue;
+            } else {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    std::vector<std::shared_ptr<CommitMessage>> GetCommitMessages(const 
std::string& path,
+                                                                  int32_t 
version) const {
+        auto file_system = GetFileSystem();
+        EXPECT_OK_AND_ASSIGN(std::unique_ptr<FileStatus> file, 
file_system->GetFileStatus(path));
+        std::vector<uint8_t> buffer(file->GetLen(), 0);
+
+        EXPECT_OK_AND_ASSIGN(std::unique_ptr<InputStream> in_stream, 
file_system->Open(path));
+        EXPECT_TRUE(in_stream);
+        EXPECT_OK_AND_ASSIGN([[maybe_unused]] int32_t length,
+                             
in_stream->Read(reinterpret_cast<char*>(buffer.data()), buffer.size()))
+        EXPECT_OK(in_stream->Close());
+        auto pool = GetDefaultPool();
+
+        EXPECT_OK_AND_ASSIGN(
+            std::vector<std::shared_ptr<CommitMessage>> ret,
+            CommitMessage::DeserializeList(version, 
reinterpret_cast<char*>(buffer.data()),
+                                           buffer.size(), pool));
+        return ret;
+    }
+
+    std::shared_ptr<FileSystem> GetFileSystem() const {
+        return file_system_;
+    }
+
+    std::unique_ptr<UniqueTestDirectory> dir_;
+    std::string test_root_;
+    std::string table_path_;
+    std::shared_ptr<FileSystem> file_system_;
+    arrow::FieldVector fields_;
+};
+
+TEST_F(FileStoreCommitImplTest, TestCommit) {
+    CommitContextBuilder context_builder(table_path_, "commit_user_1");
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<CommitContext> commit_context,
+                         context_builder.AddOption(Options::MANIFEST_FORMAT, 
"orc")
+                             .AddOption(Options::MANIFEST_TARGET_FILE_SIZE, 
"8mb")
+                             .AddOption(Options::FILE_SYSTEM, "local")
+                             .Finish());
+
+    ASSERT_OK_AND_ASSIGN(auto commit, 
FileStoreCommit::Create(std::move(commit_context)));
+
+    std::vector<std::shared_ptr<CommitMessage>> msgs =
+        GetCommitMessages(paimon::test::GetDataDir() +
+                              
"/orc/append_09.db/append_09/commit_messages/commit_messages-01",
+                          /*version=*/3);
+    ASSERT_GT(msgs.size(), 0);
+    ASSERT_OK(commit->Commit(msgs));
+    ASSERT_NOK_WITH_MSG(commit->GetLastCommitTableRequest(),
+                        "renaming snapshot commit do not support get last 
commit table request");
+    std::shared_ptr<Metrics> metrics = commit->GetCommitMetrics();
+    ASSERT_TRUE(metrics);
+    ASSERT_OK_AND_ASSIGN(uint64_t counter,
+                         
metrics->GetCounter(CommitMetrics::LAST_COMMIT_ATTEMPTS));
+    ASSERT_EQ(1u, counter);
+    ASSERT_OK_AND_ASSIGN(
+        bool exist, file_system_->Exists(PathUtil::JoinPath(table_path_, 
"snapshot/snapshot-1")));
+    ASSERT_TRUE(exist);
+}
+
+TEST_F(FileStoreCommitImplTest, TestRESTCatalogCommit) {
+    TimezoneGuard guard("Asia/Shanghai");
+    CommitContextBuilder context_builder(table_path_, "commit_user_1");
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<CommitContext> commit_context,
+                         context_builder.AddOption(Options::MANIFEST_FORMAT, 
"orc")
+                             .AddOption(Options::MANIFEST_TARGET_FILE_SIZE, 
"8mb")
+                             .AddOption(Options::FILE_SYSTEM, "local")
+                             .UseRESTCatalogCommit(true)
+                             .Finish());
+
+    ASSERT_OK_AND_ASSIGN(auto commit, 
FileStoreCommit::Create(std::move(commit_context)));
+
+    std::vector<std::shared_ptr<CommitMessage>> msgs =
+        GetCommitMessages(paimon::test::GetDataDir() +
+                              
"/orc/append_09.db/append_09/commit_messages/commit_messages-01",
+                          /*version=*/3);
+    ASSERT_GT(msgs.size(), 0);
+    ASSERT_NOK_WITH_MSG(commit->GetLastCommitTableRequest(),
+                        "Should call Commit first before 
GetLastCommitTableRequest.");
+    ASSERT_OK(commit->Commit(msgs));
+
+    ASSERT_OK_AND_ASSIGN(std::string commit_table_request_str, 
commit->GetLastCommitTableRequest());
+    ASSERT_OK_AND_ASSIGN(CommitTableRequest commit_table_request,
+                         
CommitTableRequest::FromJsonString(commit_table_request_str));
+    Snapshot expected_snapshot(
+        /*version=*/3, /*id=*/1, /*schema_id=*/0,
+        
/*base_manifest_list=*/"manifest-list-3879e56f-2f27-49ae-a2f3-3dcbb8eb0beb-0",
+        /*base_manifest_list_size=*/291,
+        
/*delta_manifest_list=*/"manifest-list-3879e56f-2f27-49ae-a2f3-3dcbb8eb0beb-1",
+        /*delta_manifest_list_size=*/1342, 
/*changelog_manifest_list=*/std::nullopt,
+        /*changelog_manifest_list_size=*/std::nullopt, 
/*index_manifest=*/std::nullopt,
+        /*commit_user=*/"commit_user_1", 
/*commit_identifier=*/9223372036854775807,
+        /*commit_kind=*/Snapshot::CommitKind::Append(), 
/*time_millis=*/1758097357597,
+        /*log_offsets=*/std::map<int32_t, int64_t>(), /*total_record_count=*/5,
+        /*delta_record_count=*/5, /*changelog_record_count=*/0, 
/*watermark=*/std::nullopt,
+        /*statistics=*/std::nullopt, /*properties=*/std::nullopt, 
/*next_row_id=*/0);
+    std::vector<PartitionStatistics> expected_partition_statistics = {
+        PartitionStatistics(/*spec=*/{{"f1", "20"}}, /*record_count=*/1, 
/*file_size_in_bytes=*/541,
+                            /*file_count=*/1,
+                            /*last_file_creation_time=*/1724090888743l - 
28800000l,
+                            /*total_buckets=*/-1),
+        PartitionStatistics(/*spec=*/{{"f1", "10"}}, /*record_count=*/4,
+                            /*file_size_in_bytes=*/1118, /*file_count=*/2,
+                            /*last_file_creation_time=*/1724090888727l - 
28800000l,
+                            /*total_buckets=*/-1)};
+    CommitTableRequest expected_commit_table_request(expected_snapshot,
+                                                     
expected_partition_statistics);
+    
ASSERT_TRUE(commit_table_request.TEST_Equal(expected_commit_table_request));
+
+    std::shared_ptr<Metrics> metrics = commit->GetCommitMetrics();
+    ASSERT_TRUE(metrics);
+    ASSERT_OK_AND_ASSIGN(uint64_t counter,
+                         
metrics->GetCounter(CommitMetrics::LAST_COMMIT_ATTEMPTS));
+    ASSERT_EQ(1u, counter);
+    ASSERT_OK_AND_ASSIGN(
+        bool exist, file_system_->Exists(PathUtil::JoinPath(table_path_, 
"snapshot/snapshot-1")));
+    ASSERT_FALSE(exist);
+}
+
+TEST_F(FileStoreCommitImplTest, 
TestCommitWithConflictSnapshotAndRetryTenTimes) {
+    std::string test_data_path = paimon::test::GetDataDir() + 
"/orc/append_09.db/append_09/";
+    auto dir = UniqueTestDirectory::Create();
+    std::string table_path = dir->Str();
+    ASSERT_TRUE(TestUtil::CopyDirectory(test_data_path, table_path));
+    ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileSystem> fs,
+                         FileSystemFactory::Get("gmock_fs", table_path, {}));
+    CommitContextBuilder context_builder(table_path, "commit_user_1");
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<CommitContext> commit_context,
+                         context_builder.AddOption(Options::MANIFEST_FORMAT, 
"orc")
+                             .AddOption(Options::MANIFEST_TARGET_FILE_SIZE, 
"8mb")
+                             .AddOption(Options::COMMIT_MAX_RETRIES, "10")
+                             .WithFileSystem(fs)
+                             .Finish());
+
+    ASSERT_OK_AND_ASSIGN(auto commit, 
FileStoreCommit::Create(std::move(commit_context)));
+    std::string latest_hint = PathUtil::JoinPath(table_path, 
"snapshot/LATEST");
+
+    auto* mock_fs = dynamic_cast<GmockFileSystem*>(fs.get());
+    EXPECT_CALL(*mock_fs, ReadFile(testing::StrEq(latest_hint), testing::_))
+        .WillRepeatedly(testing::Invoke([](const std::string& path, 
std::string* content) {
+            *content = "-1";
+            return Status::OK();
+        }));
+    EXPECT_CALL(*mock_fs, ListDir(testing::_, 
testing::_)).Times(testing::AnyNumber());
+    EXPECT_CALL(*mock_fs,
+                ListDir(testing::StrEq(PathUtil::JoinPath(table_path, 
"snapshot")), testing::_))
+        .WillRepeatedly(
+            testing::Invoke([](const std::string& directory,
+                               std::vector<std::unique_ptr<BasicFileStatus>>* 
file_status_list) {
+                return Status::OK();
+            }));
+
+    std::vector<std::shared_ptr<CommitMessage>> msgs =
+        GetCommitMessages(paimon::test::GetDataDir() +
+                              
"/orc/append_09.db/append_09/commit_messages/commit_messages-01",
+                          /*version=*/3);
+    ASSERT_GT(msgs.size(), 0);
+    ASSERT_NOK(commit->Commit(msgs));
+}
+
+TEST_F(FileStoreCommitImplTest, TestCommitWithConflictSnapshotAndRetryOnce) {
+    std::string test_data_path = paimon::test::GetDataDir() + 
"/orc/append_09.db/append_09/";
+    auto dir = UniqueTestDirectory::Create();
+    std::string table_path = dir->Str();
+    ASSERT_TRUE(TestUtil::CopyDirectory(test_data_path, table_path));
+    ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileSystem> fs,
+                         FileSystemFactory::Get("gmock_fs", table_path, {}));
+    CommitContextBuilder context_builder(table_path, "commit_user_1");
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<CommitContext> commit_context,
+                         context_builder.AddOption(Options::MANIFEST_FORMAT, 
"orc")
+                             .AddOption(Options::MANIFEST_TARGET_FILE_SIZE, 
"8mb")
+                             .WithFileSystem(fs)
+                             .Finish());
+
+    ASSERT_OK_AND_ASSIGN(auto commit, 
FileStoreCommit::Create(std::move(commit_context)));
+    std::string latest_hint = PathUtil::JoinPath(table_path, 
"snapshot/LATEST");
+    auto* mock_fs = dynamic_cast<GmockFileSystem*>(fs.get());
+    EXPECT_CALL(*mock_fs, ReadFile(testing::StrEq(latest_hint), testing::_))
+        .WillRepeatedly(testing::Invoke([](const std::string& path, 
std::string* content) {
+            *content = "-1";
+            return Status::OK();
+        }));
+
+    EXPECT_CALL(
+        *mock_fs,
+        ReadFile(testing::StrEq(PathUtil::JoinPath(table_path, 
"snapshot/snapshot-5")), testing::_))
+        .WillOnce(testing::Invoke([&](const std::string& path, std::string* 
content) {
+            return mock_fs->FileSystem::ReadFile(path, content);
+        }));
+
+    EXPECT_CALL(*mock_fs, ListDir(testing::_, 
testing::_)).Times(testing::AnyNumber());
+    EXPECT_CALL(*mock_fs,
+                ListDir(testing::StrEq(PathUtil::JoinPath(table_path, 
"snapshot")), testing::_))
+        .WillOnce(
+            testing::Invoke([](const std::string& directory,
+                               std::vector<std::unique_ptr<BasicFileStatus>>* 
file_status_list) {
+                return Status::OK();
+            }))
+        .WillRepeatedly(
+            testing::Invoke([&](const std::string& directory,
+                                std::vector<std::unique_ptr<BasicFileStatus>>* 
file_status_list) {
+                return mock_fs->LocalFileSystem::ListDir(directory, 
file_status_list);
+            }));
+
+    std::vector<std::shared_ptr<CommitMessage>> msgs =
+        GetCommitMessages(paimon::test::GetDataDir() +
+                              
"/orc/append_09.db/append_09/commit_messages/commit_messages-01",
+                          /*version=*/3);
+    ASSERT_GT(msgs.size(), 0);
+    ASSERT_OK(commit->Commit(msgs));
+    std::shared_ptr<Metrics> metrics = commit->GetCommitMetrics();
+    ASSERT_TRUE(metrics);
+    ASSERT_OK_AND_ASSIGN(uint64_t counter,
+                         
metrics->GetCounter(CommitMetrics::LAST_COMMIT_ATTEMPTS));
+    ASSERT_EQ(2u, counter);
+    ASSERT_OK_AND_ASSIGN(
+        bool exist, file_system_->Exists(PathUtil::JoinPath(table_path, 
"snapshot/snapshot-6")));
+    ASSERT_TRUE(exist);
+}
+
+TEST_F(FileStoreCommitImplTest, 
TestCommitWithAtomicWriteSnapshotTimeoutAndActuallySucceed) {
+    std::string test_data_path = paimon::test::GetDataDir() + 
"/orc/append_09.db/append_09/";
+    auto dir = UniqueTestDirectory::Create();
+    std::string table_path = dir->Str();
+    ASSERT_TRUE(TestUtil::CopyDirectory(test_data_path, table_path));
+    ASSERT_OK_AND_ASSIGN(std::shared_ptr<FileSystem> fs,
+                         FileSystemFactory::Get("gmock_fs", table_path, {}));
+    CommitContextBuilder context_builder(table_path, "commit_user_1");
+    ASSERT_OK_AND_ASSIGN(std::unique_ptr<CommitContext> commit_context,
+                         context_builder.AddOption(Options::MANIFEST_FORMAT, 
"orc")
+                             .AddOption(Options::MANIFEST_TARGET_FILE_SIZE, 
"8mb")
+                             .WithFileSystem(fs)
+                             .Finish());
+
+    ASSERT_OK_AND_ASSIGN(auto commit, 
FileStoreCommit::Create(std::move(commit_context)));
+    std::string new_snapshot_6 = PathUtil::JoinPath(table_path, 
"snapshot/snapshot-6");
+    auto* mock_fs = dynamic_cast<GmockFileSystem*>(fs.get());
+    EXPECT_CALL(*mock_fs, AtomicStore(testing::StrEq(new_snapshot_6), 
testing::_))
+        .WillOnce(testing::Invoke([&](const std::string& path, const 
std::string& content) {
+            // to mock atomic store timeout actually succeed
+            auto s = mock_fs->FileSystem::AtomicStore(path, content);
+            assert(s.ok());
+            return Status::IOError("atomic write snapshot failed");

Review Comment:
   Using `assert(s.ok())` inside the mock action makes the test behavior depend 
on `NDEBUG` (asserts compiled out), and can hide failures where the underlying 
`AtomicStore` didn't actually succeed. Prefer returning the underlying status 
when it fails, while still simulating the timeout/error when it succeeds.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to