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

wangdan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-pegasus.git


The following commit(s) were added to refs/heads/master by this push:
     new fdbd6ebb4 feat(utils): update `get_last_component()` to support 
`std::string_view` (#2299)
fdbd6ebb4 is described below

commit fdbd6ebb460b97d39c027837217c1a41de1b3671
Author: Dan Wang <[email protected]>
AuthorDate: Mon Oct 13 11:39:39 2025 +0800

    feat(utils): update `get_last_component()` to support `std::string_view` 
(#2299)
    
    Update `get_last_component()` to support `std::string_view`, making it 
adaptable
    to different use cases such as `std::string_view`, `std::string`, and 
C-style strings.
    
    This enhancement allows the function to be used in various scenarios, 
including parsing
    log file names, replica data directory names, and ZooKeeper node names.
    
    Additionally, the implementation of `get_last_component()` has been 
refactored to
    leverage `std::string_view`’s built-in methods for cleaner and more 
efficient code.
---
 src/replica/log_file.cpp                       | 119 ++++++++++++------------
 src/replica/log_file.h                         |   4 +-
 src/replica/replica_stub.cpp                   |  17 ++--
 src/replica/replica_stub.h                     |   6 +-
 src/replica/test/log_file_test.cpp             | 115 ++++++++++++++++++++++-
 src/replica/test/replica_dir_test.cpp          |  47 ++++++++--
 src/replica/test/replica_disk_migrate_test.cpp |   4 +-
 src/utils/strings.cpp                          |  18 +---
 src/utils/strings.h                            |   3 +-
 src/utils/test/utils.cpp                       | 122 +++++++++++++++++++++++--
 src/zookeeper/lock_struct.cpp                  |   4 +-
 11 files changed, 348 insertions(+), 111 deletions(-)

diff --git a/src/replica/log_file.cpp b/src/replica/log_file.cpp
index ab88a3e2e..9ebb16e09 100644
--- a/src/replica/log_file.cpp
+++ b/src/replica/log_file.cpp
@@ -26,10 +26,11 @@
 
 #include "log_file.h"
 
-#include <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
+#include <fmt/core.h>
+#include <cinttypes>
+#include <cstdio>
+#include <cstring>
+#include <string_view>
 #include <utility>
 #include <vector>
 
@@ -46,6 +47,7 @@
 #include "utils/fmt_logging.h"
 #include "utils/latency_tracer.h"
 #include "utils/ports.h"
+#include "utils/string_conv.h"
 #include "utils/strings.h"
 
 namespace dsn {
@@ -55,90 +57,91 @@ class task_tracker;
 namespace replication {
 
 log_file::~log_file() { close(); }
-/*static */ log_file_ptr log_file::open_read(const char *path, /*out*/ 
error_code &err)
+
+#define LOG_FILE_NAME_VALIDATOR(expr, message, path)                           
                    \
+    LOG_AND_RETURN_NOT_TRUE(WARNING, expr, dsn::ERR_INVALID_PARAMETERS, 
message, path);
+
+#define VALIDATE_LOG_FILE_NAME(validator, expr, path)                          
                    \
+    validator(expr,                                                            
                    \
+              "invalid log file name (its format should be 
log.<index>.<start_offset>): "          \
+              "path = {}",                                                     
                    \
+              path)
+
+#define RETURN_IF_LOG_FILE_NAME_INVALID(expr, path)                            
                    \
+    VALIDATE_LOG_FILE_NAME(LOG_FILE_NAME_VALIDATOR, expr, path)
+
+#define CHECK_LOG_FILE_NAME(expr, path) VALIDATE_LOG_FILE_NAME(CHECK, expr, 
path)
+
+/*static*/ error_code
+log_file::parse_log_file_name(const char *path, int &index, int64_t 
&start_offset)
 {
-    char splitters[] = {'\\', '/', 0};
-    std::string name = utils::get_last_component(std::string(path), splitters);
+    const auto file_name = dsn::utils::get_last_component(path, "\\/");
 
-    // log.index.start_offset
-    if (name.length() < strlen("log.") || name.substr(0, strlen("log.")) != 
std::string("log.")) {
-        err = ERR_INVALID_PARAMETERS;
-        LOG_WARNING("invalid log path {}", path);
-        return nullptr;
-    }
+    // The format of the log file name: log.<index>.<start_offset>
+    constexpr std::string_view kLogPrefix("log.");
 
-    auto pos = name.find_first_of('.');
-    CHECK(pos != std::string::npos, "invalid log_file, name = {}", name);
-    auto pos2 = name.find_first_of('.', pos + 1);
-    if (pos2 == std::string::npos) {
-        err = ERR_INVALID_PARAMETERS;
-        LOG_WARNING("invalid log path {}", path);
-        return nullptr;
-    }
+    // TODO(wangdan): from C++20, consider using 
std::string_view::starts_with() instead.
+    RETURN_IF_LOG_FILE_NAME_INVALID(file_name.rfind(kLogPrefix, 0) == 0, path);
 
-    /* so the log file format is log.index_str.start_offset_str */
-    std::string index_str = name.substr(pos + 1, pos2 - pos - 1);
-    std::string start_offset_str = name.substr(pos2 + 1);
-    if (index_str.empty() || start_offset_str.empty()) {
-        err = ERR_INVALID_PARAMETERS;
-        LOG_WARNING("invalid log path {}", path);
-        return nullptr;
-    }
+    const auto begin = kLogPrefix.size();
+    const auto end = file_name.find_first_of('.', begin);
+    RETURN_IF_LOG_FILE_NAME_INVALID(end != std::string::npos, path);
 
-    char *p = nullptr;
-    int index = static_cast<int>(strtol(index_str.c_str(), &p, 10));
-    if (*p != 0) {
-        err = ERR_INVALID_PARAMETERS;
-        LOG_WARNING("invalid log path {}", path);
-        return nullptr;
-    }
-    int64_t start_offset = 
static_cast<int64_t>(strtoll(start_offset_str.c_str(), &p, 10));
-    if (*p != 0) {
-        err = ERR_INVALID_PARAMETERS;
-        LOG_WARNING("invalid log path {}", path);
-        return nullptr;
+    const auto index_str = file_name.substr(begin, end - begin);
+    const auto start_offset_str = file_name.substr(end + 1);
+    RETURN_IF_LOG_FILE_NAME_INVALID(!index_str.empty() && 
!start_offset_str.empty(), path);
+    RETURN_IF_LOG_FILE_NAME_INVALID(dsn::buf2numeric(index_str, index), path);
+    RETURN_IF_LOG_FILE_NAME_INVALID(dsn::buf2numeric(start_offset_str, 
start_offset), path);
+
+    return ERR_OK;
+}
+
+/*static*/ log_file_ptr log_file::open_read(const char *path, /*out*/ 
error_code &err)
+{
+    int index{0};
+    int64_t start_offset{0};
+    err = parse_log_file_name(path, index, start_offset);
+    if (err != ERR_OK) {
+        return {};
     }
 
     disk_file *hfile = file::open(path, file::FileOpenType::kReadOnly);
-    if (!hfile) {
+    if (hfile == nullptr) {
         err = ERR_FILE_OPERATION_FAILED;
         LOG_WARNING("open log file {} failed", path);
-        return nullptr;
+        return {};
     }
 
-    auto lf = new log_file(path, hfile, index, start_offset, true);
+    log_file_ptr lf(new log_file(path, hfile, index, start_offset, true));
     lf->reset_stream();
+
     blob hdr_blob;
     err = lf->read_next_log_block(hdr_blob);
     if (err == ERR_INVALID_DATA || err == ERR_INCOMPLETE_DATA || err == 
ERR_HANDLE_EOF ||
         err == ERR_FILE_OPERATION_FAILED) {
-        std::string removed = std::string(path) + ".removed";
+        const auto new_path = fmt::format("{}.removed", path);
         LOG_ERROR("read first log entry of file {} failed, err = {}. Rename 
the file to {}",
                   path,
                   err,
-                  removed);
-        delete lf;
-        lf = nullptr;
+                  new_path);
 
-        // rename file on failure
-        dsn::utils::filesystem::rename_path(path, removed);
+        // Rename file on failure.
+        dsn::utils::filesystem::rename_path(path, new_path);
 
-        return nullptr;
+        return {};
     }
 
     binary_reader reader(std::move(hdr_blob));
     lf->read_file_header(reader);
     if (!lf->is_right_header()) {
-        std::string removed = std::string(path) + ".removed";
-        LOG_ERROR("invalid log file header of file {}. Rename the file to {}", 
path, removed);
-        delete lf;
-        lf = nullptr;
+        const auto new_path = fmt::format("{}.removed", path);
+        LOG_ERROR("invalid log file header of file {}. Rename the file to {}", 
path, new_path);
 
-        // rename file on failure
-        dsn::utils::filesystem::rename_path(path, removed);
+        // Rename file on failure.
+        dsn::utils::filesystem::rename_path(path, new_path);
 
         err = ERR_INVALID_DATA;
-        return nullptr;
+        return {};
     }
 
     err = ERR_OK;
diff --git a/src/replica/log_file.h b/src/replica/log_file.h
index 74700310e..57c1115f6 100644
--- a/src/replica/log_file.h
+++ b/src/replica/log_file.h
@@ -209,10 +209,12 @@ public:
     const disk_file *file_handle() const { return _handle; }
 
 private:
+    static error_code parse_log_file_name(const char *path, int &index, 
int64_t &start_offset);
+
     // make private, user should create log_file through open_read() or 
open_write()
     log_file(const char *path, disk_file *handle, int index, int64_t 
start_offset, bool is_read);
 
-private:
+    friend class ParseLogFileNameTest;
     friend class mock_log_file;
 
     uint32_t _crc32;
diff --git a/src/replica/replica_stub.cpp b/src/replica/replica_stub.cpp
index 5d64de07f..7f0b6cfed 100644
--- a/src/replica/replica_stub.cpp
+++ b/src/replica/replica_stub.cpp
@@ -2247,24 +2247,23 @@ replica *replica_stub::new_replica(gpid gpid,
     return new_replica(gpid, app, restore_if_necessary, 
is_duplication_follower, {});
 }
 
-/*static*/ std::string replica_stub::get_replica_dir_name(const std::string 
&dir)
+/*static*/ std::string_view 
replica_stub::get_replica_dir_name(std::string_view dir)
 {
-    static const char splitters[] = {'\\', '/', 0};
-    return utils::get_last_component(dir, splitters);
+    return utils::get_last_component(dir, "\\/");
 }
 
-/* static */ bool
-replica_stub::parse_replica_dir_name(const std::string &dir_name, gpid &pid, 
std::string &app_type)
+/*static*/ bool
+replica_stub::parse_replica_dir_name(std::string_view dir_name, gpid &pid, 
std::string &app_type)
 {
     std::vector<uint32_t> ids(2, 0);
     size_t begin = 0;
     for (auto &id : ids) {
-        size_t end = dir_name.find('.', begin);
+        const size_t end = dir_name.find('.', begin);
         if (end == std::string::npos) {
             return false;
         }
 
-        if (!buf2uint32(std::string_view(dir_name.data() + begin, end - 
begin), id)) {
+        if (!buf2uint32(dir_name.substr(begin, end - begin), id)) {
             return false;
         }
 
@@ -2278,9 +2277,7 @@ replica_stub::parse_replica_dir_name(const std::string 
&dir_name, gpid &pid, std
     pid.set_app_id(static_cast<int32_t>(ids[0]));
     pid.set_partition_index(static_cast<int32_t>(ids[1]));
 
-    // TODO(wangdan): the 3rd parameter `count` does not support default 
argument for CentOS 7
-    // (gcc 7.3.1). After CentOS 7 is deprecated, consider dropping 
std::string::npos.
-    app_type.assign(dir_name, begin, std::string::npos);
+    app_type = dir_name.substr(begin);
     return true;
 }
 
diff --git a/src/replica/replica_stub.h b/src/replica/replica_stub.h
index 57070215b..10f831949 100644
--- a/src/replica/replica_stub.h
+++ b/src/replica/replica_stub.h
@@ -34,6 +34,7 @@
 #include <map>
 #include <memory>
 #include <string>
+#include <string_view>
 #include <tuple>
 #include <unordered_map>
 #include <utility>
@@ -394,11 +395,10 @@ private:
 
     // Get the replica dir name from a potentially longer path (`dir` could be 
an absolute
     // or relative path).
-    static std::string get_replica_dir_name(const std::string &dir);
+    static std::string_view get_replica_dir_name(std::string_view dir);
 
     // Parse app id, partition id and app type from the replica dir name.
-    static bool
-    parse_replica_dir_name(const std::string &dir_name, gpid &pid, std::string 
&app_type);
+    static bool parse_replica_dir_name(std::string_view dir_name, gpid &pid, 
std::string &app_type);
 
     // Load an existing replica which is located in `dn` with `replica_dir`. 
Usually each
     // different `dn` represents a unique disk. `replica_dir` is the absolute 
path of the
diff --git a/src/replica/test/log_file_test.cpp 
b/src/replica/test/log_file_test.cpp
index c4abb02b2..bb2b2581d 100644
--- a/src/replica/test/log_file_test.cpp
+++ b/src/replica/test/log_file_test.cpp
@@ -15,7 +15,8 @@
 // specific language governing permissions and limitations
 // under the License.
 
-#include <stddef.h>
+#include <cstddef>
+#include <cstdint>
 #include <memory>
 #include <string>
 #include <vector>
@@ -30,8 +31,113 @@
 #include "utils/error_code.h"
 #include "utils/filesystem.h"
 
-namespace dsn {
-namespace replication {
+namespace dsn::replication {
+
+struct parse_log_file_name_case
+{
+    const char *path;
+    error_code expected_err;
+    int expected_index;
+    int64_t expected_start_offset;
+};
+
+class ParseLogFileNameTest : public 
testing::TestWithParam<parse_log_file_name_case>
+{
+public:
+    static void test_parse_log_file_name()
+    {
+        const auto &test_case = GetParam();
+
+        int actual_index{0};
+        int64_t actual_start_offset{0};
+        ASSERT_EQ(test_case.expected_err,
+                  log_file::parse_log_file_name(test_case.path, actual_index, 
actual_start_offset));
+
+        if (test_case.expected_err != ERR_OK) {
+            return;
+        }
+
+        EXPECT_EQ(test_case.expected_index, actual_index);
+        EXPECT_EQ(test_case.expected_start_offset, actual_start_offset);
+    }
+};
+
+TEST_P(ParseLogFileNameTest, ParseLogFileName) { test_parse_log_file_name(); }
+
+const std::vector<parse_log_file_name_case> parse_log_file_name_tests{
+    // Empty file name.
+    {"", ERR_INVALID_PARAMETERS, 0, 0},
+
+    // Invalid prefix.
+    {".", ERR_INVALID_PARAMETERS, 0, 0},
+    {"g", ERR_INVALID_PARAMETERS, 0, 0},
+    {".g", ERR_INVALID_PARAMETERS, 0, 0},
+    {".gol", ERR_INVALID_PARAMETERS, 0, 0},
+    {"lo", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log_", ERR_INVALID_PARAMETERS, 0, 0},
+    {"logs.", ERR_INVALID_PARAMETERS, 0, 0},
+
+    // No field.
+    {"log.", ERR_INVALID_PARAMETERS, 0, 0},
+
+    // Only one field.
+    {"log.0", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log_0", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log.012", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log_012", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log.1", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log_1", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log.123", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log_123", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log..0", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log..1", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log..123", ERR_INVALID_PARAMETERS, 0, 0},
+
+    // Empty fields.
+    {"log._", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log..", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log...", ERR_INVALID_PARAMETERS, 0, 0},
+
+    // Invalid splitters.
+    {"log.0_0", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log_0.0", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log.0_1", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log_0.1", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log.1_0", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log_1.0", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log.1_1", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log_1.1", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log.123_456", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log_123.456", ERR_INVALID_PARAMETERS, 0, 0},
+
+    // Invalid characters.
+    {"log.123.abc", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log.123.a12", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log.123.1a2", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log.123.12a", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log.abc.123", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log.a12.123", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log.1a2.123", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log.12a.123", ERR_INVALID_PARAMETERS, 0, 0},
+
+    // Too many fields.
+    {"log.123.456.789", ERR_INVALID_PARAMETERS, 0, 0},
+
+    // Numbers that overflow.
+    {"log.2147483648.123", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log.123.9223372036854775808", ERR_INVALID_PARAMETERS, 0, 0},
+    {"log.2147483648.9223372036854775808", ERR_INVALID_PARAMETERS, 0, 0},
+
+    // Valid fields.
+    {"log.123.456", ERR_OK, 123, 456},
+    {"log.2147483647.123", ERR_OK, 2147483647, 123},
+    {"log.123.9223372036854775807", ERR_OK, 123, 9223372036854775807},
+};
+
+INSTANTIATE_TEST_SUITE_P(LogFileTest,
+                         ParseLogFileNameTest,
+                         testing::ValuesIn(parse_log_file_name_tests));
 
 class log_file_test : public replica_test_base
 {
@@ -99,5 +205,4 @@ TEST_P(log_file_test, commit_log_blocks)
     ASSERT_EQ(tsk->get_aio_context()->file_offset, appender->start_offset() - 
_start_offset);
 }
 
-} // namespace replication
-} // namespace dsn
+} // namespace dsn::replication
diff --git a/src/replica/test/replica_dir_test.cpp 
b/src/replica/test/replica_dir_test.cpp
index 3bc64d74f..12ff39411 100644
--- a/src/replica/test/replica_dir_test.cpp
+++ b/src/replica/test/replica_dir_test.cpp
@@ -16,6 +16,7 @@
 // under the License.
 
 #include <string>
+#include <string_view>
 #include <vector>
 
 #include "common/gpid.h"
@@ -26,22 +27,35 @@ namespace dsn::replication {
 
 struct get_replica_dir_name_case
 {
-    std::string path;
-    std::string expected_replica_dir_name;
+    const char *path;
+    std::string_view expected_replica_dir_name;
 };
 
 class GetReplicaDirNameTest : public 
testing::TestWithParam<get_replica_dir_name_case>
 {
 public:
+    template <typename TPath>
     static void test_get_replica_dir_name()
     {
         const auto &test_case = GetParam();
-        const auto &actual_replica_dir_name = 
replica_stub::get_replica_dir_name(test_case.path);
+
+        const TPath path(test_case.path);
+        const auto actual_replica_dir_name = 
replica_stub::get_replica_dir_name(path);
         EXPECT_EQ(test_case.expected_replica_dir_name, 
actual_replica_dir_name);
     }
 };
 
-TEST_P(GetReplicaDirNameTest, GetReplicaDirName) { 
test_get_replica_dir_name(); }
+TEST_P(GetReplicaDirNameTest, GetReplicaDirNameCString)
+{
+    test_get_replica_dir_name<const char *>();
+}
+
+TEST_P(GetReplicaDirNameTest, GetReplicaDirNameString) { 
test_get_replica_dir_name<std::string>(); }
+
+TEST_P(GetReplicaDirNameTest, GetReplicaDirNameStringView)
+{
+    test_get_replica_dir_name<std::string_view>();
+}
 
 const std::vector<get_replica_dir_name_case> get_replica_dir_name_tests{
     // Linux absolute path and non-empty dir name.
@@ -68,7 +82,7 @@ INSTANTIATE_TEST_SUITE_P(ReplicaDirTest,
 
 struct parse_replica_dir_name_case
 {
-    std::string replica_dir_name;
+    const char *replica_dir_name;
     bool ok;
     gpid expected_pid;
     std::string expected_app_type;
@@ -77,15 +91,17 @@ struct parse_replica_dir_name_case
 class ParseReplicaDirNameTest : public 
testing::TestWithParam<parse_replica_dir_name_case>
 {
 public:
+    template <typename TDirName>
     static void test_parse_replica_dir_name()
     {
         const auto &test_case = GetParam();
 
+        const TDirName replica_dir_name(test_case.replica_dir_name);
         gpid actual_pid;
         std::string actual_app_type;
-        ASSERT_EQ(test_case.ok,
-                  replica_stub::parse_replica_dir_name(
-                      test_case.replica_dir_name, actual_pid, 
actual_app_type));
+        ASSERT_EQ(
+            test_case.ok,
+            replica_stub::parse_replica_dir_name(replica_dir_name, actual_pid, 
actual_app_type));
         if (!test_case.ok) {
             return;
         }
@@ -95,7 +111,20 @@ public:
     }
 };
 
-TEST_P(ParseReplicaDirNameTest, ParseReplicaDirName) { 
test_parse_replica_dir_name(); }
+TEST_P(ParseReplicaDirNameTest, ParseReplicaDirNameCString)
+{
+    test_parse_replica_dir_name<const char *>();
+}
+
+TEST_P(ParseReplicaDirNameTest, ParseReplicaDirNameString)
+{
+    test_parse_replica_dir_name<std::string>();
+}
+
+TEST_P(ParseReplicaDirNameTest, ParseReplicaDirNameStringView)
+{
+    test_parse_replica_dir_name<std::string_view>();
+}
 
 const std::vector<parse_replica_dir_name_case> parse_replica_dir_name_tests{
     // Empty dir name.
diff --git a/src/replica/test/replica_disk_migrate_test.cpp 
b/src/replica/test/replica_disk_migrate_test.cpp
index df410925e..63298e147 100644
--- a/src/replica/test/replica_disk_migrate_test.cpp
+++ b/src/replica/test/replica_disk_migrate_test.cpp
@@ -165,7 +165,9 @@ TEST_P(replica_disk_migrate_test, on_migrate_replica)
     request.origin_disk = "tag_1";
     request.target_disk = "tag_2";
     stub->on_disk_migrate(fake_migrate_rpc);
-    get_replica(request.pid)->tracker()->wait_outstanding_tasks();
+    auto rep = get_replica(request.pid);
+    ASSERT_TRUE(rep != nullptr);
+    rep->tracker()->wait_outstanding_tasks();
     ASSERT_EQ(response.err, ERR_OK);
 }
 
diff --git a/src/utils/strings.cpp b/src/utils/strings.cpp
index 85e32936c..240753645 100644
--- a/src/utils/strings.cpp
+++ b/src/utils/strings.cpp
@@ -159,22 +159,10 @@ error_s pattern_match(const std::string &str,
     return error_s::ok();
 }
 
-std::string get_last_component(const std::string &input, const char 
splitters[])
+std::string_view get_last_component(std::string_view str, std::string_view 
splitters)
 {
-    int index = -1;
-    const char *s = splitters;
-
-    while (*s != 0) {
-        auto pos = input.find_last_of(*s);
-        if (pos != std::string::npos && (static_cast<int>(pos) > index))
-            index = static_cast<int>(pos);
-        s++;
-    }
-
-    if (index != -1)
-        return input.substr(index + 1);
-    else
-        return input;
+    const auto pos = str.find_last_of(splitters);
+    return (pos == std::string_view::npos) ? str : str.substr(pos + 1);
 }
 
 namespace {
diff --git a/src/utils/strings.h b/src/utils/strings.h
index 8972e78ec..1a5008b24 100644
--- a/src/utils/strings.h
+++ b/src/utils/strings.h
@@ -31,6 +31,7 @@
 #include <list>
 #include <map>
 #include <string>
+#include <string_view>
 #include <unordered_set>
 #include <vector>
 
@@ -132,7 +133,7 @@ std::string kv_map_to_string(const std::map<std::string, 
std::string> &kv_map,
 std::string
 replace_string(std::string subject, const std::string &search, const 
std::string &replace);
 
-std::string get_last_component(const std::string &input, const char 
splitters[]);
+std::string_view get_last_component(std::string_view str, std::string_view 
splitters);
 
 char *trim_string(char *s);
 
diff --git a/src/utils/test/utils.cpp b/src/utils/test/utils.cpp
index d1888a7f5..6ec6fdc20 100644
--- a/src/utils/test/utils.cpp
+++ b/src/utils/test/utils.cpp
@@ -29,6 +29,7 @@
 #include <map>
 #include <set>
 #include <string>
+#include <string_view>
 #include <tuple>
 #include <unordered_set>
 #include <utility>
@@ -49,15 +50,124 @@
 
 namespace dsn::utils {
 
-TEST(core, get_last_component)
+struct get_last_component_case
 {
-    ASSERT_EQ("a", get_last_component("a", "/"));
-    ASSERT_EQ("b", get_last_component("a/b", "/"));
-    ASSERT_EQ("b", get_last_component("a//b", "/"));
-    ASSERT_EQ("", get_last_component("a/", "/"));
-    ASSERT_EQ("c", get_last_component("a/b_c", "/_"));
+    const char *str;
+    const char *splitters;
+    std::string_view expected_result;
+};
+
+class GetLastComponentTest : public 
testing::TestWithParam<get_last_component_case>
+{
+protected:
+    template <typename TStr>
+    void test_get_last_component()
+    {
+        const auto &test_case = GetParam();
+
+        const TStr str(test_case.str);
+        const auto actual_result = get_last_component(str, 
test_case.splitters);
+        EXPECT_EQ(test_case.expected_result, actual_result);
+    }
+};
+
+TEST_P(GetLastComponentTest, GetLastComponentCString) { 
test_get_last_component<const char *>(); }
+
+TEST_P(GetLastComponentTest, GetLastComponentString) { 
test_get_last_component<std::string>(); }
+
+TEST_P(GetLastComponentTest, GetLastComponentStringView)
+{
+    test_get_last_component<std::string_view>();
 }
 
+const std::vector<get_last_component_case> get_last_component_tests = {
+    // Empty string.
+    {"", "", ""},
+    {"", "/", ""},
+    {"", "\\/", ""},
+    {"/", "", "/"},
+
+    // The only character is a splitter.
+    {"/", "/", ""},
+    {"/", "\\/", ""},
+
+    // The only character is not a splitter.
+    {"a", "/", "a"},
+    {"a", "\\/", "a"},
+    {"/", "\\", "/"},
+
+    // There is not any splitter in multiple characters.
+    {"aa", "/", "aa"},
+    {"aa", "\\/", "aa"},
+    {"ab", "/", "ab"},
+    {"ab", "\\/", "ab"},
+    {"abc", "/", "abc"},
+    {"abc", "\\/", "abc"},
+
+    // There is only one splitter in multiple characters.
+    {"a/", "/", ""},
+    {"/a", "/", "a"},
+    {"a/", "\\/", ""},
+    {"/a", "\\/", "a"},
+    {"aa/", "/", ""},
+    {"a/a", "/", "a"},
+    {"/aa", "/", "aa"},
+    {"aa/", "\\/", ""},
+    {"a/a", "\\/", "a"},
+    {"/aa", "\\/", "aa"},
+    {"ab/", "/", ""},
+    {"a/b", "/", "b"},
+    {"/ab", "/", "ab"},
+    {"abc/", "\\/", ""},
+    {"ab/c", "\\/", "c"},
+    {"a/bc", "\\/", "bc"},
+    {"/abc", "\\/", "abc"},
+    {"abc\\", "\\/", ""},
+    {"ab\\c", "\\/", "c"},
+    {"a\\bc", "\\/", "bc"},
+    {"\\abc", "\\/", "abc"},
+
+    // There are adjacent splitters in multiple characters.
+    {"a//", "/", ""},
+    {"a/\\", "\\/", ""},
+    {"//a", "/", "a"},
+    {"\\/a", "\\/", "a"},
+    {"aa//", "/", ""},
+    {"aa\\/", "/\\", ""},
+    {"a//a", "/", "a"},
+    {"a/\\a", "/\\", "a"},
+    {"//aa", "/", "aa"},
+    {"\\/aa", "/\\", "aa"},
+    {"ab//", "/", ""},
+    {"ab/\\", "\\/", ""},
+    {"a//b", "/", "b"},
+    {"a\\/b", "\\/", "b"},
+    {"//ab", "/", "ab"},
+    {"/\\ab", "\\/", "ab"},
+    {"abc//", "/", ""},
+    {"abc\\/", "\\/", ""},
+    {"ab//c", "/", "c"},
+    {"ab/\\c", "\\/", "c"},
+    {"a//bc", "/", "bc"},
+    {"a\\/bc", "/\\", "bc"},
+    {"//abc", "/", "abc"},
+    {"\\/abc", "/\\", "abc"},
+
+    // There are multiple splitters in multiple characters.
+    {"\\a/", "/\\", ""},
+    {"a\\a/", "/\\", ""},
+    {"/aa\\", "/\\", ""},
+    {"a/b\\", "/\\", ""},
+    {"\\ab/", "/\\", ""},
+    {"a/b/c", "/", "c"},
+    {"a\\b/c", "/\\", "c"},
+    {"ab/cd\\efg", "/\\", "efg"},
+};
+
+INSTANTIATE_TEST_SUITE_P(StringTest,
+                         GetLastComponentTest,
+                         testing::ValuesIn(get_last_component_tests));
+
 TEST(core, crc)
 {
     char buffer[24];
diff --git a/src/zookeeper/lock_struct.cpp b/src/zookeeper/lock_struct.cpp
index 7ef0aee1a..2f0c0073d 100644
--- a/src/zookeeper/lock_struct.cpp
+++ b/src/zookeeper/lock_struct.cpp
@@ -30,6 +30,7 @@
 #include <functional>
 #include <memory>
 #include <string>
+#include <string_view>
 #include <type_traits>
 #include <utility>
 #include <vector>
@@ -613,8 +614,7 @@ void lock_struct::after_create_locknode(lock_struct_ptr 
_this,
         return;
     }
 
-    char splitter[] = {'/', 0};
-    _this->_myself._node_seq_name = utils::get_last_component(*path, splitter);
+    _this->_myself._node_seq_name = utils::get_last_component(*path, "/");
     _this->_myself._sequence_id = 
parse_seq_path(_this->_myself._node_seq_name);
     CHECK_NE_MSG(_this->_myself._sequence_id, -1, "invalid seq path created");
     LOG_INFO("create seq/ephe node in dir({}) ok, my_sequence_id({})",


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

Reply via email to