This is an automated email from the ASF dual-hosted git repository.
xyji pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-opendal.git
The following commit(s) were added to refs/heads/main by this push:
new 42a0e994d feat(bindings/C): implement capability (#3479)
42a0e994d is described below
commit 42a0e994d35ddcdb4b3b47936939f08e3a194436
Author: Xinyou Ji <[email protected]>
AuthorDate: Tue Nov 7 00:40:51 2023 -0500
feat(bindings/C): implement capability (#3479)
* add capability support
* polish
---
bindings/c/Makefile | 4 +-
bindings/c/include/opendal.h | 257 +++++++++++++++++++++++++++++++++++-
bindings/c/src/lib.rs | 2 +
bindings/c/src/metadata.rs | 2 +-
bindings/c/src/operator_info.rs | 279 ++++++++++++++++++++++++++++++++++++++++
bindings/c/tests/opinfo.cpp | 111 ++++++++++++++++
6 files changed, 652 insertions(+), 3 deletions(-)
diff --git a/bindings/c/Makefile b/bindings/c/Makefile
index 245d4facb..d7a1bd244 100644
--- a/bindings/c/Makefile
+++ b/bindings/c/Makefile
@@ -25,7 +25,7 @@ LDFLAGS=-L$(RPATH) -Wl,-rpath,$(RPATH)
LIBS=-lopendal_c -lgtest -lpthread
-VALGRIND=valgrind --error-exitcode=1 --leak-check=full --
+VALGRIND=valgrind --error-exitcode=1 --leak-check=full --
.PHONY: all
all: build test examples
@@ -46,9 +46,11 @@ test:
$(CXX) tests/bdd.cpp -o $(OBJ_DIR)/bdd $(CXXFLAGS) $(LDFLAGS) $(LIBS)
$(CXX) tests/list.cpp -o $(OBJ_DIR)/list $(CXXFLAGS) $(LDFLAGS) $(LIBS)
$(CXX) tests/error_msg.cpp -o $(OBJ_DIR)/error_msg $(CXXFLAGS)
$(LDFLAGS) $(LIBS)
+ $(CXX) tests/opinfo.cpp -o $(OBJ_DIR)/opinfo $(CXXFLAGS) $(LDFLAGS)
$(LIBS)
$(OBJ_DIR)/bdd
$(OBJ_DIR)/list
$(OBJ_DIR)/error_msg
+ $(OBJ_DIR)/opinfo
.PHONY: test_memory_leak
memory_leak:
diff --git a/bindings/c/include/opendal.h b/bindings/c/include/opendal.h
index cc4339151..55586cd83 100644
--- a/bindings/c/include/opendal.h
+++ b/bindings/c/include/opendal.h
@@ -207,6 +207,11 @@ typedef struct HashMap_String__String
HashMap_String__String;
*/
typedef struct Metadata Metadata;
+/**
+ * Metadata for operator, users can use this metadata to get information of
operator.
+ */
+typedef struct OperatorInfo OperatorInfo;
+
/**
* \brief opendal_bytes carries raw-bytes with its length
*
@@ -295,7 +300,7 @@ typedef struct opendal_lister {
} opendal_lister;
/**
- * \brief Carries all metadata associated with a path.
+ * \brief Carries all metadata associated with a **path**.
*
* The metadata of the "thing" under a path. Please **only** use the
opendal_metadata
* with our provided API, e.g. opendal_metadata_content_length().
@@ -478,6 +483,199 @@ typedef struct opendal_result_list {
struct opendal_error *error;
} opendal_result_list;
+/**
+ * \brief Metadata for **operator**, users can use this metadata to get
information
+ * of operator.
+ */
+typedef struct opendal_operator_info {
+ struct OperatorInfo *inner;
+} opendal_operator_info;
+
+/**
+ * \brief Capability is used to describe what operations are supported
+ * by current Operator.
+ */
+typedef struct opendal_capability {
+ /**
+ * If operator supports stat.
+ */
+ bool stat;
+ /**
+ * If operator supports stat with if match.
+ */
+ bool stat_with_if_match;
+ /**
+ * If operator supports stat with if none match.
+ */
+ bool stat_with_if_none_match;
+ /**
+ * If operator supports read.
+ */
+ bool read;
+ /**
+ * If operator supports seek on returning reader.
+ */
+ bool read_can_seek;
+ /**
+ * If operator supports next on returning reader.
+ */
+ bool read_can_next;
+ /**
+ * If operator supports read with range.
+ */
+ bool read_with_range;
+ /**
+ * If operator supports read with if match.
+ */
+ bool read_with_if_match;
+ /**
+ * If operator supports read with if none match.
+ */
+ bool read_with_if_none_match;
+ /**
+ * if operator supports read with override cache control.
+ */
+ bool read_with_override_cache_control;
+ /**
+ * if operator supports read with override content disposition.
+ */
+ bool read_with_override_content_disposition;
+ /**
+ * if operator supports read with override content type.
+ */
+ bool read_with_override_content_type;
+ /**
+ * If operator supports write.
+ */
+ bool write;
+ /**
+ * If operator supports write can be called in multi times.
+ */
+ bool write_can_multi;
+ /**
+ * If operator supports write with empty content.
+ */
+ bool write_can_empty;
+ /**
+ * If operator supports write by append.
+ */
+ bool write_can_append;
+ /**
+ * If operator supports write with content type.
+ */
+ bool write_with_content_type;
+ /**
+ * If operator supports write with content disposition.
+ */
+ bool write_with_content_disposition;
+ /**
+ * If operator supports write with cache control.
+ */
+ bool write_with_cache_control;
+ /**
+ * write_multi_max_size is the max size that services support in write_multi.
+ *
+ * For example, AWS S3 supports 5GiB as max in write_multi.
+ *
+ * If it is not set, this will be zero
+ */
+ uintptr_t write_multi_max_size;
+ /**
+ * write_multi_min_size is the min size that services support in write_multi.
+ *
+ * For example, AWS S3 requires at least 5MiB in write_multi expect the last
one.
+ *
+ * If it is not set, this will be zero
+ */
+ uintptr_t write_multi_min_size;
+ /**
+ * write_multi_align_size is the align size that services required in
write_multi.
+ *
+ * For example, Google GCS requires align size to 256KiB in write_multi.
+ *
+ * If it is not set, this will be zero
+ */
+ uintptr_t write_multi_align_size;
+ /**
+ * write_total_max_size is the max size that services support in write_total.
+ *
+ * For example, Cloudflare D1 supports 1MB as max in write_total.
+ *
+ * If it is not set, this will be zero
+ */
+ uintptr_t write_total_max_size;
+ /**
+ * If operator supports create dir.
+ */
+ bool create_dir;
+ /**
+ * If operator supports delete.
+ */
+ bool delete_;
+ /**
+ * If operator supports copy.
+ */
+ bool copy;
+ /**
+ * If operator supports rename.
+ */
+ bool rename;
+ /**
+ * If operator supports list.
+ */
+ bool list;
+ /**
+ * If backend supports list with limit.
+ */
+ bool list_with_limit;
+ /**
+ * If backend supports list with start after.
+ */
+ bool list_with_start_after;
+ /**
+ * If backend support list with using slash as delimiter.
+ */
+ bool list_with_delimiter_slash;
+ /**
+ * If backend supports list without delimiter.
+ */
+ bool list_without_delimiter;
+ /**
+ * If operator supports presign.
+ */
+ bool presign;
+ /**
+ * If operator supports presign read.
+ */
+ bool presign_read;
+ /**
+ * If operator supports presign stat.
+ */
+ bool presign_stat;
+ /**
+ * If operator supports presign write.
+ */
+ bool presign_write;
+ /**
+ * If operator supports batch.
+ */
+ bool batch;
+ /**
+ * If operator supports batch delete.
+ */
+ bool batch_delete;
+ /**
+ * The max operations that operator supports in batch.
+ *
+ * If it is not set, this will be zero
+ */
+ uintptr_t batch_max_operations;
+ /**
+ * If operator supports blocking.
+ */
+ bool blocking;
+} opendal_capability;
+
/**
* \brief The is the result type returned by opendal_reader_read().
* The result type contains a size field, which is the size of the data read,
@@ -951,6 +1149,63 @@ struct opendal_result_stat opendal_operator_stat(const
struct opendal_operator *
struct opendal_result_list opendal_operator_list(const struct opendal_operator
*op,
const char *path);
+/**
+ * \brief Get information of underlying accessor.
+ *
+ * # Example
+ *
+ * ```C
+ * /// suppose you have a memory-backed opendal_operator* named op
+ * char *scheme;
+ * opendal_operator_info *info = opendal_operator_info_new(op);
+ *
+ * scheme = opendal_operator_info_get_scheme(info);
+ * assert(!strcmp(scheme, "memory"));
+ *
+ * /// free the heap memory
+ * free(scheme);
+ * opendal_operator_info_free(info);
+ * ```
+ */
+struct opendal_operator_info *opendal_operator_info_new(const struct
opendal_operator *op);
+
+/**
+ * \brief Free the heap-allocated opendal_operator_info
+ */
+void opendal_operator_info_free(struct opendal_operator_info *ptr);
+
+/**
+ * \brief Return the nul-terminated operator's scheme, i.e. service
+ *
+ * \note: The string is on heap, remember to free it
+ */
+char *opendal_operator_info_get_scheme(const struct opendal_operator_info
*self);
+
+/**
+ * \brief Return the nul-terminated operator's working root path
+ *
+ * \note: The string is on heap, remember to free it
+ */
+char *opendal_operator_info_get_root(const struct opendal_operator_info *self);
+
+/**
+ * \brief Return the nul-terminated operator backend's name, could be empty if
underlying backend has no
+ * namespace concept.
+ *
+ * \note: The string is on heap, remember to free it
+ */
+char *opendal_operator_info_get_name(const struct opendal_operator_info *self);
+
+/**
+ * \brief Return the operator's full capability
+ */
+struct opendal_capability opendal_operator_info_get_full_capability(const
struct opendal_operator_info *self);
+
+/**
+ * \brief Return the operator's native capability
+ */
+struct opendal_capability opendal_operator_info_get_native_capability(const
struct opendal_operator_info *self);
+
/**
* \brief Frees the heap memory used by the opendal_bytes
*/
diff --git a/bindings/c/src/lib.rs b/bindings/c/src/lib.rs
index 76f26381a..a4429813b 100644
--- a/bindings/c/src/lib.rs
+++ b/bindings/c/src/lib.rs
@@ -43,6 +43,8 @@ pub use metadata::opendal_metadata;
mod operator;
pub use operator::opendal_operator;
+mod operator_info;
+
mod result;
pub use result::opendal_result_is_exist;
pub use result::opendal_result_list;
diff --git a/bindings/c/src/metadata.rs b/bindings/c/src/metadata.rs
index 15c38bbd2..a28d72d7b 100644
--- a/bindings/c/src/metadata.rs
+++ b/bindings/c/src/metadata.rs
@@ -17,7 +17,7 @@
use ::opendal as core;
-/// \brief Carries all metadata associated with a path.
+/// \brief Carries all metadata associated with a **path**.
///
/// The metadata of the "thing" under a path. Please **only** use the
opendal_metadata
/// with our provided API, e.g. opendal_metadata_content_length().
diff --git a/bindings/c/src/operator_info.rs b/bindings/c/src/operator_info.rs
new file mode 100644
index 000000000..2a700a6e7
--- /dev/null
+++ b/bindings/c/src/operator_info.rs
@@ -0,0 +1,279 @@
+// 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.
+
+use std::ffi::{c_char, CString};
+
+use ::opendal as core;
+
+use crate::opendal_operator;
+
+/// \brief Metadata for **operator**, users can use this metadata to get
information
+/// of operator.
+#[repr(C)]
+pub struct opendal_operator_info {
+ pub inner: *mut core::OperatorInfo,
+}
+
+/// \brief Capability is used to describe what operations are supported
+/// by current Operator.
+#[repr(C)]
+pub struct opendal_capability {
+ /// If operator supports stat.
+ pub stat: bool,
+ /// If operator supports stat with if match.
+ pub stat_with_if_match: bool,
+ /// If operator supports stat with if none match.
+ pub stat_with_if_none_match: bool,
+
+ /// If operator supports read.
+ pub read: bool,
+ /// If operator supports seek on returning reader.
+ pub read_can_seek: bool,
+ /// If operator supports next on returning reader.
+ pub read_can_next: bool,
+ /// If operator supports read with range.
+ pub read_with_range: bool,
+ /// If operator supports read with if match.
+ pub read_with_if_match: bool,
+ /// If operator supports read with if none match.
+ pub read_with_if_none_match: bool,
+ /// if operator supports read with override cache control.
+ pub read_with_override_cache_control: bool,
+ /// if operator supports read with override content disposition.
+ pub read_with_override_content_disposition: bool,
+ /// if operator supports read with override content type.
+ pub read_with_override_content_type: bool,
+
+ /// If operator supports write.
+ pub write: bool,
+ /// If operator supports write can be called in multi times.
+ pub write_can_multi: bool,
+ /// If operator supports write with empty content.
+ pub write_can_empty: bool,
+ /// If operator supports write by append.
+ pub write_can_append: bool,
+ /// If operator supports write with content type.
+ pub write_with_content_type: bool,
+ /// If operator supports write with content disposition.
+ pub write_with_content_disposition: bool,
+ /// If operator supports write with cache control.
+ pub write_with_cache_control: bool,
+ /// write_multi_max_size is the max size that services support in
write_multi.
+ ///
+ /// For example, AWS S3 supports 5GiB as max in write_multi.
+ ///
+ /// If it is not set, this will be zero
+ pub write_multi_max_size: usize,
+ /// write_multi_min_size is the min size that services support in
write_multi.
+ ///
+ /// For example, AWS S3 requires at least 5MiB in write_multi expect the
last one.
+ ///
+ /// If it is not set, this will be zero
+ pub write_multi_min_size: usize,
+ /// write_multi_align_size is the align size that services required in
write_multi.
+ ///
+ /// For example, Google GCS requires align size to 256KiB in write_multi.
+ ///
+ /// If it is not set, this will be zero
+ pub write_multi_align_size: usize,
+ /// write_total_max_size is the max size that services support in
write_total.
+ ///
+ /// For example, Cloudflare D1 supports 1MB as max in write_total.
+ ///
+ /// If it is not set, this will be zero
+ pub write_total_max_size: usize,
+
+ /// If operator supports create dir.
+ pub create_dir: bool,
+
+ /// If operator supports delete.
+ pub delete: bool,
+
+ /// If operator supports copy.
+ pub copy: bool,
+
+ /// If operator supports rename.
+ pub rename: bool,
+
+ /// If operator supports list.
+ pub list: bool,
+ /// If backend supports list with limit.
+ pub list_with_limit: bool,
+ /// If backend supports list with start after.
+ pub list_with_start_after: bool,
+ /// If backend support list with using slash as delimiter.
+ pub list_with_delimiter_slash: bool,
+ /// If backend supports list without delimiter.
+ pub list_without_delimiter: bool,
+
+ /// If operator supports presign.
+ pub presign: bool,
+ /// If operator supports presign read.
+ pub presign_read: bool,
+ /// If operator supports presign stat.
+ pub presign_stat: bool,
+ /// If operator supports presign write.
+ pub presign_write: bool,
+
+ /// If operator supports batch.
+ pub batch: bool,
+ /// If operator supports batch delete.
+ pub batch_delete: bool,
+ /// The max operations that operator supports in batch.
+ ///
+ /// If it is not set, this will be zero
+ pub batch_max_operations: usize,
+
+ /// If operator supports blocking.
+ pub blocking: bool,
+}
+
+impl opendal_operator_info {
+ /// \brief Get information of underlying accessor.
+ ///
+ /// # Example
+ ///
+ /// ```C
+ /// /// suppose you have a memory-backed opendal_operator* named op
+ /// char *scheme;
+ /// opendal_operator_info *info = opendal_operator_info_new(op);
+ ///
+ /// scheme = opendal_operator_info_get_scheme(info);
+ /// assert(!strcmp(scheme, "memory"));
+ ///
+ /// /// free the heap memory
+ /// free(scheme);
+ /// opendal_operator_info_free(info);
+ /// ```
+ #[no_mangle]
+ pub unsafe extern "C" fn opendal_operator_info_new(op: *const
opendal_operator) -> *mut Self {
+ let op = (*op).as_ref();
+ let info = op.info();
+
+ Box::into_raw(Box::new(Self {
+ inner: Box::into_raw(Box::new(info)),
+ }))
+ }
+
+ /// \brief Free the heap-allocated opendal_operator_info
+ #[no_mangle]
+ pub unsafe extern "C" fn opendal_operator_info_free(ptr: *mut Self) {
+ unsafe {
+ let _ = Box::from_raw((*ptr).inner);
+ let _ = Box::from_raw(ptr);
+ }
+ }
+
+ /// \brief Return the nul-terminated operator's scheme, i.e. service
+ ///
+ /// \note: The string is on heap, remember to free it
+ #[no_mangle]
+ pub unsafe extern "C" fn opendal_operator_info_get_scheme(&self) -> *mut
c_char {
+ let scheme = (*self.inner).scheme().to_string();
+ CString::new(scheme)
+ .expect("CString::new failed in opendal_operator_info_get_root")
+ .into_raw()
+ }
+
+ /// \brief Return the nul-terminated operator's working root path
+ ///
+ /// \note: The string is on heap, remember to free it
+ #[no_mangle]
+ pub unsafe extern "C" fn opendal_operator_info_get_root(&self) -> *mut
c_char {
+ let root = (*self.inner).root();
+ CString::new(root)
+ .expect("CString::new failed in opendal_operator_info_get_root")
+ .into_raw()
+ }
+
+ /// \brief Return the nul-terminated operator backend's name, could be
empty if underlying backend has no
+ /// namespace concept.
+ ///
+ /// \note: The string is on heap, remember to free it
+ #[no_mangle]
+ pub unsafe extern "C" fn opendal_operator_info_get_name(&self) -> *mut
c_char {
+ let name = (*self.inner).name();
+ CString::new(name)
+ .expect("CString::new failed in opendal_operator_info_get_name")
+ .into_raw()
+ }
+
+ /// \brief Return the operator's full capability
+ #[no_mangle]
+ pub unsafe extern "C" fn opendal_operator_info_get_full_capability(
+ &self,
+ ) -> opendal_capability {
+ let cap = (*self.inner).full_capability();
+ cap.into()
+ }
+
+ /// \brief Return the operator's native capability
+ #[no_mangle]
+ pub unsafe extern "C" fn opendal_operator_info_get_native_capability(
+ &self,
+ ) -> opendal_capability {
+ let cap = (*self.inner).native_capability();
+ cap.into()
+ }
+}
+
+impl From<core::Capability> for opendal_capability {
+ fn from(value: core::Capability) -> Self {
+ Self {
+ stat: value.stat,
+ stat_with_if_match: value.stat_with_if_match,
+ stat_with_if_none_match: value.stat_with_if_none_match,
+ read: value.read,
+ read_can_seek: value.read_can_seek,
+ read_can_next: value.read_can_next,
+ read_with_range: value.read_with_range,
+ read_with_if_match: value.read_with_if_match,
+ read_with_if_none_match: value.read_with_if_none_match,
+ read_with_override_content_type:
value.read_with_override_content_type,
+ read_with_override_cache_control:
value.read_with_override_cache_control,
+ read_with_override_content_disposition:
value.read_with_override_content_disposition,
+ write: value.write,
+ write_can_multi: value.write_can_multi,
+ write_can_empty: value.write_can_empty,
+ write_can_append: value.write_can_append,
+ write_with_content_type: value.write_with_content_type,
+ write_with_content_disposition:
value.write_with_content_disposition,
+ write_with_cache_control: value.write_with_cache_control,
+ write_multi_max_size: value.write_multi_max_size.unwrap_or(0),
+ write_multi_min_size: value.write_multi_min_size.unwrap_or(0),
+ write_multi_align_size: value.write_multi_align_size.unwrap_or(0),
+ write_total_max_size: value.write_total_max_size.unwrap_or(0),
+ create_dir: value.create_dir,
+ delete: value.delete,
+ copy: value.copy,
+ rename: value.rename,
+ list: value.list,
+ list_with_limit: value.list_with_limit,
+ list_with_start_after: value.list_with_start_after,
+ list_without_delimiter: value.list_without_delimiter,
+ list_with_delimiter_slash: value.list_with_delimiter_slash,
+ presign: value.presign,
+ presign_read: value.presign_read,
+ presign_stat: value.presign_stat,
+ presign_write: value.presign_write,
+ batch: value.batch,
+ batch_delete: value.batch_delete,
+ batch_max_operations: value.batch_max_operations.unwrap_or(0),
+ blocking: value.blocking,
+ }
+ }
+}
diff --git a/bindings/c/tests/opinfo.cpp b/bindings/c/tests/opinfo.cpp
new file mode 100644
index 000000000..7239b7543
--- /dev/null
+++ b/bindings/c/tests/opinfo.cpp
@@ -0,0 +1,111 @@
+/*
+ * 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 "gtest/gtest.h"
+extern "C" {
+#include "opendal.h"
+}
+
+class OpendalOperatorInfoTest : public ::testing::Test {
+protected:
+ opendal_operator* p;
+ opendal_operator_info* info;
+ std::string root;
+ std::string scheme;
+
+ // set up a brand new operator
+ void SetUp() override
+ {
+ this->root = std::string("/myroot/");
+ this->scheme = std::string("memory");
+
+ opendal_operator_options* options = opendal_operator_options_new();
+ opendal_operator_options_set(options, "root", this->root.c_str());
+
+ opendal_result_operator_new result =
opendal_operator_new(this->scheme.c_str(), options);
+ EXPECT_TRUE(result.error == nullptr);
+
+ this->p = result.op;
+ EXPECT_TRUE(this->p);
+
+ this->info = opendal_operator_info_new(this->p);
+ EXPECT_TRUE(this->info);
+
+ opendal_operator_options_free(options);
+ }
+
+ void TearDown() override
+ {
+ opendal_operator_free(this->p);
+ opendal_operator_info_free(this->info);
+ }
+};
+
+// We test the capability set by **memory** service.
+TEST_F(OpendalOperatorInfoTest, CapabilityTest)
+{
+ opendal_capability full_cap =
opendal_operator_info_get_full_capability(this->info);
+ opendal_capability native_cap =
opendal_operator_info_get_native_capability(this->info);
+ opendal_capability caps[2] = { full_cap, native_cap };
+
+ for (int i = 0; i < 2; ++i) {
+ opendal_capability cap = caps[i];
+
+ EXPECT_TRUE(cap.blocking);
+
+ EXPECT_TRUE(cap.read);
+ EXPECT_TRUE(cap.read_can_seek);
+ EXPECT_TRUE(cap.read_can_next);
+ EXPECT_TRUE(cap.read_with_range);
+ EXPECT_TRUE(cap.stat);
+
+ EXPECT_TRUE(cap.write);
+ EXPECT_TRUE(cap.write_can_empty);
+ EXPECT_TRUE(cap.create_dir);
+
+ EXPECT_TRUE(cap.delete_);
+
+ EXPECT_TRUE(cap.list);
+ EXPECT_TRUE(cap.list_without_delimiter);
+
+ EXPECT_TRUE(cap.copy);
+
+ EXPECT_TRUE(cap.rename);
+ }
+}
+
+TEST_F(OpendalOperatorInfoTest, InfoTest)
+{
+ char *scheme, *root;
+ scheme = opendal_operator_info_get_scheme(this->info);
+ root = opendal_operator_info_get_root(this->info);
+
+ EXPECT_TRUE(!strcmp(scheme, this->scheme.c_str()));
+ EXPECT_TRUE(!strcmp(root, this->root.c_str()));
+
+ // remember to free the strings
+ free(scheme);
+ free(root);
+}
+
+int main(int argc, char** argv)
+{
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}