You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by as...@apache.org on 2025/08/14 23:41:47 UTC

(opendal) branch main updated: feat(bindings/cpp): enable user-configurable features for storage services (#6503)

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

asukaminato pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new 08880efe5 feat(bindings/cpp): enable user-configurable features for storage services (#6503)
08880efe5 is described below

commit 08880efe58c489978905fcbf8711d1dd9f388aef
Author: 木之本澪 <ki...@gmail.com>
AuthorDate: Fri Aug 15 07:41:42 2025 +0800

    feat(bindings/cpp): enable user-configurable features for storage services (#6503)
    
    * feat(bindings/cpp): remove hardcoded service features from Cargo.toml
    
    Remove hardcoded service dependencies to enable user-configurable features.
      Only keep 'blocking' feature as it's required for C++ bindings.
    
    Related to #6250
    
    * feat(bindings/cpp): add FEATURES cache variable to CMakeLists.txt
    
    Related to #6250
    
    * feat(bindings/cpp): implement feature merging logic for configurable builds
    
    Related to #6250
    
    * docs(bindings/cpp): add FEATURES option documentation
    
    Related to #6250
    
    * ci(bindings/cpp): add FEATURES support to behavior test action
    
    Related to #6250
    
    * ci(bindings/cpp): add FEATURES parameter to all build configurations
    
    Related to #6250
    
    * feat(bindings/cpp): add conditional RocksDB linking support
    
    Related to #6250
    
    * feat(bindings/cpp): add conditional HDFS linking support
    
    Related to #6250
    
    * test(bindings/cpp): skip ReadAfterModification test for alluxio
    
    Alluxio doesn't support file overwrite without explicit overwrite option.
    
    Related to #6250
    
    * test(bindings/cpp): skip OverwriteExistingFile test for alluxio
    
    Alluxio doesn't support file overwrite without explicit overwrite option.
    
    Related to #6250
    
    * test(bindings/cpp): skip WriteAppendBehavior test for alluxio
    
    Alluxio doesn't support file overwrite without explicit overwrite option.
    
    Related to #6250
    
    * test(bindings/cpp): skip DeleteSpecialCharFile test for alluxio
    
    Alluxio doesn't support file overwrite without explicit overwrite option.
    
    Related to #6250
    
    * test(bindings/cpp): skip ListEmptyDirectory test for alluxio
    
    Alluxio has different list behavior that includes unexpected paths.
    
    Related to #6250
    
    * test(bindings/cpp): skip ListDirectoryWithFiles test for alluxio
    
    Alluxio has different list behavior that includes unexpected paths.
    
    Related to #6250
    
    * test(bindings/cpp): skip ListNestedDirectories test for alluxio
    
    Alluxio has different list behavior that includes unexpected paths.
    
    Related to #6250
    
    * test(bindings/cpp): skip ListManyFiles test for alluxio
    
    Alluxio has different list behavior that includes unexpected paths.
    
    Related to #6250
    
    * test(bindings/cpp): skip ListSpecialCharNames test for alluxio
    
    Alluxio has different list behavior that includes unexpected paths.
    
    Related to #6250
    
    * test(bindings/cpp): skip ListerIterator test for alluxio
    
    Alluxio has different list behavior that includes unexpected paths.
    
    Related to #6250
    
    * fix(bindings/cpp): improve HDFS library detection using HADOOP_HOME
    
    Related to #6250
    
    * refactor(bindings/cpp): rename FEATURES to OPENDAL_FEATURES
    
    Related to #6250
    
    * docs(bindings/cpp): update README to reflect OPENDAL_FEATURES rename
    
    Related to #6250
    
    * test(bindings/cpp): use adaptive logic for ListEmptyDirectory test
    
    Related to #6250
    
    * test(bindings/cpp): use adaptive logic for ListDirectoryWithFiles test
    
    Related to #6250
    
    * test(bindings/cpp): use adaptive logic for ListNestedDirectories test
    
    Related to #6250
    
    * test(bindings/cpp): use adaptive logic for ListManyFiles test
    
    Related to #6250
    
    * test(bindings/cpp): use adaptive logic for ListSpecialCharNames test
    
    Related to #6250
    
    * test(bindings/cpp): use adaptive logic for ListerIterator test
    
    Related to #6250
    
    * test(bindings/cpp): use exception-based approach for ReadAfterModification test
    
    Related to #6250
    
    * test(bindings/cpp): use exception-based approach for OverwriteExistingFile test
    
    Related to #6250
    
    * test(bindings/cpp): use exception-based approach for WriteAppendBehavior test
    
    Related to #6250
    
    * test(bindings/cpp): use exception-based approach for DeleteSpecialCharFile test
    
    Related to #6250
    
    * refactor(bindings/cpp): remove manual RocksDB and HDFS linking
    
    Related to #6250
    
    * Apply suggestions from code review
    
    ---------
    
    Co-authored-by: Asuka Minato <i...@asukaminato.eu.org>
---
 .../actions/test_behavior_binding_cpp/action.yaml  |   3 +
 .github/workflows/ci_bindings_cpp.yml              |   8 +-
 bindings/cpp/CMakeLists.txt                        |  12 ++-
 bindings/cpp/Cargo.toml                            |  20 +---
 bindings/cpp/README.md                             |   1 +
 bindings/cpp/tests/behavior/delete_test.cpp        |  24 +++--
 bindings/cpp/tests/behavior/list_test.cpp          | 109 +++++++++++++++------
 bindings/cpp/tests/behavior/read_test.cpp          |  22 +++--
 bindings/cpp/tests/behavior/write_test.cpp         |  40 ++++++--
 9 files changed, 163 insertions(+), 76 deletions(-)

diff --git a/.github/actions/test_behavior_binding_cpp/action.yaml b/.github/actions/test_behavior_binding_cpp/action.yaml
index b3deb755f..74fc1989c 100644
--- a/.github/actions/test_behavior_binding_cpp/action.yaml
+++ b/.github/actions/test_behavior_binding_cpp/action.yaml
@@ -49,6 +49,7 @@ runs:
                   -DOPENDAL_ENABLE_TESTING=ON \
                   -DOPENDAL_DEV=ON \
                   -DOPENDAL_ENABLE_ASYNC=ON \
+                  -DFEATURES="opendal/${{ inputs.feature }}" \
                   -DCMAKE_CXX_STANDARD=20 \
                   ..
                 cmake --build . --config Release
@@ -59,6 +60,7 @@ runs:
                   -DOPENDAL_ENABLE_TESTING=ON \
                   -DOPENDAL_DEV=ON \
                   -DOPENDAL_ENABLE_ASYNC=ON \
+                  -DFEATURES="opendal/${{ inputs.feature }}" \
                   -DCMAKE_CXX_COMPILER=clang++ \
                   -DCMAKE_CXX_STANDARD=20 \
                   ..
@@ -70,6 +72,7 @@ runs:
                   -DOPENDAL_ENABLE_TESTING=ON \
                   -DOPENDAL_DEV=ON \
                   -DOPENDAL_ENABLE_ASYNC=ON \
+                  -DFEATURES="opendal/${{ inputs.feature }}" \
                   -DCMAKE_CXX_COMPILER=clang++ \
                   -DCMAKE_CXX_STANDARD=20 \
                   ..
diff --git a/.github/workflows/ci_bindings_cpp.yml b/.github/workflows/ci_bindings_cpp.yml
index 4765e1de9..0c824f897 100644
--- a/.github/workflows/ci_bindings_cpp.yml
+++ b/.github/workflows/ci_bindings_cpp.yml
@@ -63,7 +63,7 @@ jobs:
         run: |
           mkdir build
           cd build
-          cmake -GNinja -DOPENDAL_DEV=ON ..
+          cmake -GNinja -DOPENDAL_DEV=ON -DFEATURES=opendal/services-memory ..
           ninja
           ./opendal_cpp_test
 
@@ -72,7 +72,7 @@ jobs:
         run: |
           mkdir build-valgrind
           cd build-valgrind
-          cmake -GNinja -DOPENDAL_ENABLE_TESTING=ON ..
+          cmake -GNinja -DOPENDAL_ENABLE_TESTING=ON -DFEATURES=opendal/services-memory ..
           ninja
           valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose ./opendal_cpp_test
 
@@ -81,7 +81,7 @@ jobs:
         run: |
           mkdir build-async
           cd build-async
-          cmake -GNinja -DOPENDAL_DEV=ON -DOPENDAL_ENABLE_ASYNC=ON -DCMAKE_CXX_COMPILER=clang++-18 ..
+          cmake -GNinja -DOPENDAL_DEV=ON -DOPENDAL_ENABLE_ASYNC=ON -DFEATURES=opendal/services-memory -DCMAKE_CXX_COMPILER=clang++-18 ..
           ninja
           ./opendal_cpp_test
 
@@ -90,7 +90,7 @@ jobs:
         run: |
           mkdir build-valgrind-async
           cd build-valgrind-async
-          cmake -GNinja -DOPENDAL_ENABLE_ASYNC=ON -DOPENDAL_ENABLE_TESTING=ON -DCMAKE_CXX_COMPILER=clang++-18 ..
+          cmake -GNinja -DOPENDAL_ENABLE_ASYNC=ON -DFEATURES=opendal/services-memory -DOPENDAL_ENABLE_TESTING=ON -DCMAKE_CXX_COMPILER=clang++-18 ..
           ninja
           valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose ./opendal_cpp_test
 
diff --git a/bindings/cpp/CMakeLists.txt b/bindings/cpp/CMakeLists.txt
index 09c502e67..c1ab0f364 100644
--- a/bindings/cpp/CMakeLists.txt
+++ b/bindings/cpp/CMakeLists.txt
@@ -39,6 +39,7 @@ option(OPENDAL_DOCS_ONLY "Only build documentation (dev only for quick ci)" OFF)
 option(OPENDAL_ENABLE_TESTING "Enable building test binary for opendal" OFF)
 option(OPENDAL_DEV "Enable dev mode" OFF)
 option(OPENDAL_ENABLE_ASYNC "Enable async mode (requires C++20)" OFF)
+set(OPENDAL_FEATURES "" CACHE STRING "opendal features")
 
 if(OPENDAL_ENABLE_ASYNC)
     set(CMAKE_CXX_STANDARD 20)
@@ -123,9 +124,17 @@ if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
 endif()
 
 if (OPENDAL_ENABLE_ASYNC)
-    list(APPEND CARGO_BUILD_FLAGS "--features" "async")
+    if (OPENDAL_FEATURES)
+        set(OPENDAL_FEATURES "${OPENDAL_FEATURES},async")
+    else()
+        set(OPENDAL_FEATURES "async")
+    endif ()
 endif()
 
+if (OPENDAL_FEATURES)
+    list(APPEND CARGO_BUILD_FLAGS "--features" "${OPENDAL_FEATURES}")
+endif ()
+
 add_custom_target(cargo_build
         COMMAND cargo build --manifest-path ${CARGO_MANIFEST} ${CARGO_BUILD_FLAGS}
         BYPRODUCTS ${RUST_BRIDGE_CPP} ${RUST_LIB} ${RUST_HEADER_FILE}
@@ -144,6 +153,7 @@ if (OPENDAL_ENABLE_ASYNC)
 endif()
 target_link_libraries(opendal_cpp PUBLIC ${RUST_LIB})
 target_link_libraries(opendal_cpp PRIVATE ${CMAKE_DL_LIBS})
+
 set_target_properties(opendal_cpp
         PROPERTIES ADDITIONAL_CLEAN_FILES ${CARGO_TARGET_DIR}
 )
diff --git a/bindings/cpp/Cargo.toml b/bindings/cpp/Cargo.toml
index a414a1d39..42a331182 100644
--- a/bindings/cpp/Cargo.toml
+++ b/bindings/cpp/Cargo.toml
@@ -36,25 +36,7 @@ cxx = "1.0"
 cxx-async = { version = "0.1.2", optional = true }
 futures = "0.3"
 # this crate won't be published, we always use the local version
-opendal = { version = ">=0", path = "../../core", features = [
-  "blocking",
-  # These are default features before v0.46. TODO: change to optional features
-  "services-azblob",
-  "services-azdls",
-  "services-cos",
-  "services-fs",
-  "services-gcs",
-  "services-ghac",
-  "services-http",
-  "services-ipmfs",
-  "services-memory",
-  "services-obs",
-  "services-oss",
-  "services-s3",
-  "services-webdav",
-  "services-webhdfs",
-  "services-azfile",
-] }
+opendal = { version = ">=0", path = "../../core", features = ["blocking"] }
 tokio = { version = "1.27", features = ["fs", "macros", "rt-multi-thread"] }
 
 [build-dependencies]
diff --git a/bindings/cpp/README.md b/bindings/cpp/README.md
index 638f41b07..5c4a898cc 100644
--- a/bindings/cpp/README.md
+++ b/bindings/cpp/README.md
@@ -104,6 +104,7 @@ make docs
 - `OPENDAL_DOCS_ONLY`: Only build documentation. Default: `OFF`
 - `OPENDAL_ENABLE_TESTING`: Enable testing. Default: `OFF`
 - `OPENDAL_ENABLE_ASYNC`: Enable async support. Default: `OFF`
+- `OPENDAL_FEATURES`: Specify OpenDAL services to include, like `"opendal/services-s3,opendal/services-memory"`. Default: `""`
 
 ## License and Trademarks
 
diff --git a/bindings/cpp/tests/behavior/delete_test.cpp b/bindings/cpp/tests/behavior/delete_test.cpp
index 0850058b9..b02cb4c9c 100644
--- a/bindings/cpp/tests/behavior/delete_test.cpp
+++ b/bindings/cpp/tests/behavior/delete_test.cpp
@@ -102,13 +102,23 @@ OPENDAL_TEST_F(DeleteBehaviorTest, DeleteSpecialCharFile) {
     auto path = "test_with-special.chars_123/file-name_with.special.txt";
     auto content = random_string(100);
     
-    // Create the file
-    op_.Write(path, content);
-    EXPECT_TRUE(op_.Exists(path));
-    
-    // Delete the file
-    op_.Remove(path);
-    EXPECT_FALSE(op_.Exists(path));
+    // Try to create the file
+    try {
+        op_.Write(path, content);
+        EXPECT_TRUE(op_.Exists(path));
+        
+        // Delete the file
+        op_.Remove(path);
+        EXPECT_FALSE(op_.Exists(path));
+    } catch (const std::exception& e) {
+        // Check if error message indicates AlreadyExists
+        std::string error_msg = e.what();
+        if (error_msg.find("AlreadyExists") != std::string::npos) {
+            GTEST_SKIP() << "Service doesn't support file overwrite: " << error_msg;
+        } else {
+            throw; // Re-throw other errors
+        }
+    }
 }
 
 // Test deleting large file
diff --git a/bindings/cpp/tests/behavior/list_test.cpp b/bindings/cpp/tests/behavior/list_test.cpp
index b5e58b790..761a060a4 100644
--- a/bindings/cpp/tests/behavior/list_test.cpp
+++ b/bindings/cpp/tests/behavior/list_test.cpp
@@ -43,9 +43,7 @@ OPENDAL_TEST_F(ListBehaviorTest, ListEmptyDirectory) {
     // List the directory
     auto entries = op_.List(dir_path);
     
-    // Should contain at least the directory itself
-    EXPECT_GE(entries.size(), 1);
-    
+    // Directory may or may not be included in listing depending on service behavior
     bool found_dir = false;
     for (const auto& entry : entries) {
         if (entry.path == dir_path) {
@@ -54,7 +52,12 @@ OPENDAL_TEST_F(ListBehaviorTest, ListEmptyDirectory) {
             EXPECT_EQ(metadata.type, opendal::EntryMode::DIR);
         }
     }
-    EXPECT_TRUE(found_dir);
+    
+    // If directory not found in listing, verify it exists using Stat
+    if (!found_dir) {
+        auto metadata = op_.Stat(dir_path);
+        EXPECT_EQ(metadata.type, opendal::EntryMode::DIR);
+    }
 }
 
 // Test listing directory with files
@@ -76,10 +79,8 @@ OPENDAL_TEST_F(ListBehaviorTest, ListDirectoryWithFiles) {
     // List the directory
     auto entries = op_.List(dir_path);
     
-    // Should contain directory and 3 files
-    EXPECT_EQ(entries.size(), 4);
-    
-    std::set<std::string> expected_paths = {dir_path, file1_path, file2_path, file3_path};
+    // Should contain 3 files, and possibly the directory itself depending on service behavior
+    std::set<std::string> expected_paths = {file1_path, file2_path, file3_path};
     std::set<std::string> actual_paths;
     
     for (const auto& entry : entries) {
@@ -93,7 +94,17 @@ OPENDAL_TEST_F(ListBehaviorTest, ListDirectoryWithFiles) {
         }
     }
     
-    EXPECT_EQ(actual_paths, expected_paths);
+    // All files should be present
+    for (const auto& expected_path : expected_paths) {
+        EXPECT_TRUE(actual_paths.count(expected_path) > 0) << "Missing file: " << expected_path;
+    }
+    
+    // Directory may or may not be included depending on service behavior
+    if (actual_paths.count(dir_path) > 0) {
+        EXPECT_EQ(entries.size(), 4); // directory + 3 files
+    } else {
+        EXPECT_EQ(entries.size(), 3); // only 3 files
+    }
 }
 
 // Test listing nested directories
@@ -114,27 +125,45 @@ OPENDAL_TEST_F(ListBehaviorTest, ListNestedDirectories) {
     
     // List base directory
     auto entries = op_.List(base_dir);
-    
-    std::set<std::string> expected_paths = {base_dir, sub_dir, file1};
+    std::set<std::string> expected_paths = {sub_dir, file1};
     std::set<std::string> actual_paths;
     
     for (const auto& entry : entries) {
         actual_paths.insert(entry.path);
     }
     
-    EXPECT_EQ(actual_paths, expected_paths);
+    // All required paths should be present
+    for (const auto& expected_path : expected_paths) {
+        EXPECT_TRUE(actual_paths.count(expected_path) > 0) << "Missing path: " << expected_path;
+    }
+    
+    // Directory may or may not be included depending on service behavior
+    if (actual_paths.count(base_dir) > 0) {
+        EXPECT_EQ(entries.size(), 3); // directory + subdirectory + file
+    } else {
+        EXPECT_EQ(entries.size(), 2); // only subdirectory + file
+    }
     
     // List subdirectory
     auto sub_entries = op_.List(sub_dir);
-    
-    std::set<std::string> sub_expected_paths = {sub_dir, file2};
+    std::set<std::string> sub_expected_paths = {file2};
     std::set<std::string> sub_actual_paths;
     
     for (const auto& entry : sub_entries) {
         sub_actual_paths.insert(entry.path);
     }
     
-    EXPECT_EQ(sub_actual_paths, sub_expected_paths);
+    // File should be present in subdirectory listing
+    for (const auto& expected_path : sub_expected_paths) {
+        EXPECT_TRUE(sub_actual_paths.count(expected_path) > 0) << "Missing path: " << expected_path;
+    }
+    
+    // Subdirectory may or may not be included depending on service behavior
+    if (sub_actual_paths.count(sub_dir) > 0) {
+        EXPECT_EQ(sub_entries.size(), 2); // subdirectory + file
+    } else {
+        EXPECT_EQ(sub_entries.size(), 1); // only file
+    }
 }
 
 // Test listing non-existent directory
@@ -178,20 +207,24 @@ OPENDAL_TEST_F(ListBehaviorTest, ListManyFiles) {
     
     // List the directory
     auto entries = op_.List(dir_path);
-    
-    // Should contain directory + num_files
-    EXPECT_EQ(entries.size(), num_files + 1);
-    
-    // Verify all files are present
     std::set<std::string> expected_paths(file_paths.begin(), file_paths.end());
-    expected_paths.insert(dir_path);
-    
     std::set<std::string> actual_paths;
+    
     for (const auto& entry : entries) {
         actual_paths.insert(entry.path);
     }
     
-    EXPECT_EQ(actual_paths, expected_paths);
+    // All files should be present
+    for (const auto& expected_path : expected_paths) {
+        EXPECT_TRUE(actual_paths.count(expected_path) > 0) << "Missing file: " << expected_path;
+    }
+    
+    // Directory may or may not be included depending on service behavior
+    if (actual_paths.count(dir_path) > 0) {
+        EXPECT_EQ(entries.size(), num_files + 1); // directory + files
+    } else {
+        EXPECT_EQ(entries.size(), num_files); // only files
+    }
 }
 
 // Test listing with special character names
@@ -212,15 +245,24 @@ OPENDAL_TEST_F(ListBehaviorTest, ListSpecialCharNames) {
     
     // List the directory
     auto entries = op_.List(dir_path);
-    
-    std::set<std::string> expected_paths = {dir_path, file1, file2, file3};
+    std::set<std::string> expected_paths = {file1, file2, file3};
     std::set<std::string> actual_paths;
     
     for (const auto& entry : entries) {
         actual_paths.insert(entry.path);
     }
     
-    EXPECT_EQ(actual_paths, expected_paths);
+    // All files should be present
+    for (const auto& expected_path : expected_paths) {
+        EXPECT_TRUE(actual_paths.count(expected_path) > 0) << "Missing file: " << expected_path;
+    }
+    
+    // Directory may or may not be included depending on service behavior
+    if (actual_paths.count(dir_path) > 0) {
+        EXPECT_EQ(entries.size(), 4); // directory + 3 files
+    } else {
+        EXPECT_EQ(entries.size(), 3); // only 3 files
+    }
 }
 
 // Test using lister iterator
@@ -239,8 +281,7 @@ OPENDAL_TEST_F(ListBehaviorTest, ListerIterator) {
     
     // Use lister
     auto lister = op_.List(dir_path);
-    
-    std::set<std::string> expected_paths = {dir_path, file1_path, file2_path};
+    std::set<std::string> expected_paths = {file1_path, file2_path};
     std::set<std::string> actual_paths;
     
     for (const auto& entry : lister) {
@@ -254,7 +295,17 @@ OPENDAL_TEST_F(ListBehaviorTest, ListerIterator) {
         }
     }
     
-    EXPECT_EQ(actual_paths, expected_paths);
+    // All files should be present
+    for (const auto& expected_path : expected_paths) {
+        EXPECT_TRUE(actual_paths.count(expected_path) > 0) << "Missing file: " << expected_path;
+    }
+    
+    // Directory may or may not be included depending on service behavior
+    if (actual_paths.count(dir_path) > 0) {
+        EXPECT_EQ(actual_paths.size(), 3); // directory + 2 files
+    } else {
+        EXPECT_EQ(actual_paths.size(), 2); // only 2 files
+    }
 }
 
 // Test listing root directory
diff --git a/bindings/cpp/tests/behavior/read_test.cpp b/bindings/cpp/tests/behavior/read_test.cpp
index 7b5acc5dc..d5a64b500 100644
--- a/bindings/cpp/tests/behavior/read_test.cpp
+++ b/bindings/cpp/tests/behavior/read_test.cpp
@@ -157,12 +157,22 @@ OPENDAL_TEST_F(ReadBehaviorTest, ReadAfterModification) {
     auto result1 = op_.Read(path);
     EXPECT_EQ(result1, original_content);
     
-    // Modify the file
-    op_.Write(path, modified_content);
-    
-    // Verify modified content
-    auto result2 = op_.Read(path);
-    EXPECT_EQ(result2, modified_content);
+    // Try to modify the file
+    try {
+        op_.Write(path, modified_content);
+        
+        // Verify modified content
+        auto result2 = op_.Read(path);
+        EXPECT_EQ(result2, modified_content);
+    } catch (const std::exception& e) {
+        // Check if error message indicates AlreadyExists
+        std::string error_msg = e.what();
+        if (error_msg.find("AlreadyExists") != std::string::npos) {
+            GTEST_SKIP() << "Service doesn't support file overwrite: " << error_msg;
+        } else {
+            throw; // Re-throw other errors
+        }
+    }
 }
 
 // Test concurrent reads (if threading is available)
diff --git a/bindings/cpp/tests/behavior/write_test.cpp b/bindings/cpp/tests/behavior/write_test.cpp
index c7797b598..b7438a13a 100644
--- a/bindings/cpp/tests/behavior/write_test.cpp
+++ b/bindings/cpp/tests/behavior/write_test.cpp
@@ -106,11 +106,21 @@ OPENDAL_TEST_F(WriteBehaviorTest, OverwriteExistingFile) {
     auto result1 = op_.Read(path);
     EXPECT_EQ(result1, original_content);
     
-    // Overwrite with new content
-    op_.Write(path, new_content);
-    auto result2 = op_.Read(path);
-    EXPECT_EQ(result2, new_content);
-    EXPECT_NE(result2, original_content);
+    // Try to overwrite with new content
+    try {
+        op_.Write(path, new_content);
+        auto result2 = op_.Read(path);
+        EXPECT_EQ(result2, new_content);
+        EXPECT_NE(result2, original_content);
+    } catch (const std::exception& e) {
+        // Check if error message indicates AlreadyExists
+        std::string error_msg = e.what();
+        if (error_msg.find("AlreadyExists") != std::string::npos) {
+            GTEST_SKIP() << "Service doesn't support file overwrite: " << error_msg;
+        } else {
+            throw; // Re-throw other errors
+        }
+    }
 }
 
 // Test writing to nested path (should create intermediate directories)
@@ -248,11 +258,21 @@ OPENDAL_TEST_F(WriteBehaviorTest, WriteAppendBehavior) {
     auto result1 = op_.Read(path);
     EXPECT_EQ(result1, content1);
     
-    // Write second content (should overwrite, not append for normal write)
-    op_.Write(path, content2);
-    auto result2 = op_.Read(path);
-    EXPECT_EQ(result2, content2);
-    EXPECT_NE(result2, content1 + content2); // Should not be appended
+    // Try to write second content (should overwrite, not append for normal write)
+    try {
+        op_.Write(path, content2);
+        auto result2 = op_.Read(path);
+        EXPECT_EQ(result2, content2);
+        EXPECT_NE(result2, content1 + content2); // Should not be appended
+    } catch (const std::exception& e) {
+        // Check if error message indicates AlreadyExists
+        std::string error_msg = e.what();
+        if (error_msg.find("AlreadyExists") != std::string::npos) {
+            GTEST_SKIP() << "Service doesn't support file overwrite: " << error_msg;
+        } else {
+            throw; // Re-throw other errors
+        }
+    }
 }
 
 } // namespace opendal::test 
\ No newline at end of file