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