add tests and missing implementations for strings

Fri, 09 Sep 2022 20:19:08 +0200

author
Mike Becker <universe@uap-core.de>
date
Fri, 09 Sep 2022 20:19:08 +0200
changeset 583
0f3c9662f9b5
parent 582
96fa7fa6af4f
child 584
184e9ebfc3cc

add tests and missing implementations for strings

src/cx/string.h file | annotate | diff | comparison | revisions
src/string.c file | annotate | diff | comparison | revisions
test/CMakeLists.txt file | annotate | diff | comparison | revisions
test/test_string.cpp file | annotate | diff | comparison | revisions
     1.1 --- a/src/cx/string.h	Sat Sep 03 15:11:23 2022 +0200
     1.2 +++ b/src/cx/string.h	Fri Sep 09 20:19:08 2022 +0200
     1.3 @@ -78,6 +78,15 @@
     1.4   */
     1.5  typedef struct cx_string_s cxstring;
     1.6  
     1.7 +/**
     1.8 + * A literal initializer for an UCX string structure.
     1.9 + *
    1.10 + * The argument MUST be a string (const char*) \em literal.
    1.11 + *
    1.12 + * @param literal the string literal
    1.13 + */
    1.14 +#define CX_STR(literal) {literal, sizeof(literal) - 1}
    1.15 +
    1.16  #ifdef __cplusplus
    1.17  extern "C" {
    1.18  #endif
    1.19 @@ -190,9 +199,28 @@
    1.20   *
    1.21   * @param str the string to free
    1.22   */
    1.23 +__attribute__((__nonnull__))
    1.24  void cx_strfree(cxmutstr *str);
    1.25  
    1.26  /**
    1.27 + * Passes the pointer in this string to the allocators free function.
    1.28 + *
    1.29 + * The pointer in the struct is set to \c NULL and the length is set to zero.
    1.30 + *
    1.31 + * \note There is no implementation for cxstring, because it is unlikely that
    1.32 + * you ever have a \c char \c const* you are really supposed to free. If you
    1.33 + * encounter such situation, you should double-check your code.
    1.34 + *
    1.35 + * @param alloc the allocator
    1.36 + * @param str the string to free
    1.37 + */
    1.38 +__attribute__((__nonnull__))
    1.39 +void cx_strfree_a(
    1.40 +        CxAllocator *alloc,
    1.41 +        cxmutstr *str
    1.42 +);
    1.43 +
    1.44 +/**
    1.45   * Returns the accumulated length of all specified strings.
    1.46   *
    1.47   * \attention if the count argument is larger than the number of the
    1.48 @@ -720,7 +748,7 @@
    1.49   * The returned string will be allocated by \p allocator.
    1.50   *
    1.51   * If allocation fails, or the input string is empty,
    1.52 - * the returned string will point to \c NULL.
    1.53 + * the returned string will be empty.
    1.54   *
    1.55   * @param allocator the allocator to use
    1.56   * @param str the string where replacements should be applied
    1.57 @@ -730,7 +758,7 @@
    1.58   * @return the resulting string after applying the replacements
    1.59   */
    1.60  __attribute__((__warn_unused_result__, __nonnull__))
    1.61 -cxmutstr cx_strreplace_a(
    1.62 +cxmutstr cx_strreplacen_a(
    1.63          CxAllocator *allocator,
    1.64          cxstring str,
    1.65          cxstring pattern,
    1.66 @@ -748,7 +776,7 @@
    1.67   * to cx_strfree() eventually.
    1.68   *
    1.69   * If allocation fails, or the input string is empty,
    1.70 - * the returned string will point to \c NULL.
    1.71 + * the returned string will be empty.
    1.72   *
    1.73   * @param str the string where replacements should be applied
    1.74   * @param pattern the pattern to search for
    1.75 @@ -756,8 +784,47 @@
    1.76   * @param replmax maximum number of replacements
    1.77   * @return the resulting string after applying the replacements
    1.78   */
    1.79 -#define cx_strreplace(str, pattern, replacement, replmax) \
    1.80 -cx_strreplace_a(cxDefaultAllocator, str, pattern, replacement, replmax)
    1.81 +#define cx_strreplacen(str, pattern, replacement, replmax) \
    1.82 +cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, replmax)
    1.83 +
    1.84 +/**
    1.85 + * Replaces a pattern in a string with another string.
    1.86 + *
    1.87 + * The pattern is taken literally and is no regular expression.
    1.88 + *
    1.89 + * The returned string will be allocated by \p allocator.
    1.90 + *
    1.91 + * If allocation fails, or the input string is empty,
    1.92 + * the returned string will be empty.
    1.93 + *
    1.94 + * @param allocator the allocator to use
    1.95 + * @param str the string where replacements should be applied
    1.96 + * @param pattern the pattern to search for
    1.97 + * @param replacement the replacement string
    1.98 + * @return the resulting string after applying the replacements
    1.99 + */
   1.100 +#define cx_strreplace_a(allocator, str, pattern, replacement) \
   1.101 +cx_strreplacen_a(allocator, str, pattern, replacement, SIZE_MAX)
   1.102 +
   1.103 +/**
   1.104 + * Replaces a pattern in a string with another string.
   1.105 + *
   1.106 + * The pattern is taken literally and is no regular expression.
   1.107 + * Replaces at most \p replmax occurrences.
   1.108 + *
   1.109 + * The returned string will be allocated by \c malloc() and \em must be passed
   1.110 + * to cx_strfree() eventually.
   1.111 + *
   1.112 + * If allocation fails, or the input string is empty,
   1.113 + * the returned string will be empty.
   1.114 + *
   1.115 + * @param str the string where replacements should be applied
   1.116 + * @param pattern the pattern to search for
   1.117 + * @param replacement the replacement string
   1.118 + * @return the resulting string after applying the replacements
   1.119 + */
   1.120 +#define cx_strreplace(str, pattern, replacement) \
   1.121 +cx_strreplacen_a(cxDefaultAllocator, str, pattern, replacement, SIZE_MAX)
   1.122  
   1.123  #ifdef __cplusplus
   1.124  } // extern "C"
     2.1 --- a/src/string.c	Sat Sep 03 15:11:23 2022 +0200
     2.2 +++ b/src/string.c	Fri Sep 09 20:19:08 2022 +0200
     2.3 @@ -72,6 +72,15 @@
     2.4      str->length = 0;
     2.5  }
     2.6  
     2.7 +void cx_strfree_a(
     2.8 +        CxAllocator *alloc,
     2.9 +        cxmutstr *str
    2.10 +) {
    2.11 +    cxFree(alloc, str->ptr);
    2.12 +    str->ptr = NULL;
    2.13 +    str->length = 0;
    2.14 +}
    2.15 +
    2.16  size_t cx_strlen(
    2.17          size_t count,
    2.18          ...
    2.19 @@ -235,6 +244,11 @@
    2.20          return haystack;
    2.21      }
    2.22  
    2.23 +    /* optimize for single-char needles */
    2.24 +    if (needle.length == 1) {
    2.25 +        return cx_strchr(haystack, *needle.ptr);
    2.26 +    }
    2.27 +
    2.28      /*
    2.29       * IMPORTANT:
    2.30       * Our prefix table contains the prefix length PLUS ONE
    2.31 @@ -308,8 +322,55 @@
    2.32          size_t limit,
    2.33          cxstring *output
    2.34  ) {
    2.35 -    // TODO: implement
    2.36 -    return 0;
    2.37 +    /* special case: output limit is zero */
    2.38 +    if (limit == 0) return 0;
    2.39 +
    2.40 +    /* special case: delimiter is empty */
    2.41 +    if (delim.length == 0) {
    2.42 +        output[0] = string;
    2.43 +        return 1;
    2.44 +    }
    2.45 +
    2.46 +    /* special cases: delimiter is at least as large as the string */
    2.47 +    if (delim.length >= string.length) {
    2.48 +        /* exact match */
    2.49 +        if (cx_strcmp(string, delim) == 0) {
    2.50 +            output[0] = cx_strn(string.ptr, 0);
    2.51 +            output[1] = cx_strn(string.ptr + string.length, 0);
    2.52 +            return 2;
    2.53 +        } else /* no match possible */ {
    2.54 +            output[0] = string;
    2.55 +            return 1;
    2.56 +        }
    2.57 +    }
    2.58 +
    2.59 +    size_t n = 0;
    2.60 +    cxstring curpos = string;
    2.61 +    while (1) {
    2.62 +        ++n;
    2.63 +        cxstring match = cx_strstr(curpos, delim);
    2.64 +        if (match.length > 0) {
    2.65 +            /* is the limit reached? */
    2.66 +            if (n < limit) {
    2.67 +                /* copy the current string to the array */
    2.68 +                cxstring item = cx_strn(curpos.ptr, match.ptr - curpos.ptr);
    2.69 +                output[n - 1] = item;
    2.70 +                size_t processed = item.length + delim.length;
    2.71 +                curpos.ptr += processed;
    2.72 +                curpos.length -= processed;
    2.73 +            } else {
    2.74 +                /* limit reached, copy the _full_ remaining string */
    2.75 +                output[n - 1] = curpos;
    2.76 +                break;
    2.77 +            }
    2.78 +        } else {
    2.79 +            /* no more matches, copy last string */
    2.80 +            output[n - 1] = curpos;
    2.81 +            break;
    2.82 +        }
    2.83 +    }
    2.84 +
    2.85 +    return n;
    2.86  }
    2.87  
    2.88  size_t cx_strsplit_a(
    2.89 @@ -319,8 +380,29 @@
    2.90          size_t limit,
    2.91          cxstring **output
    2.92  ) {
    2.93 -    // TODO: implement
    2.94 -    return 0;
    2.95 +    /* find out how many splits we're going to make and allocate memory */
    2.96 +    size_t n = 0;
    2.97 +    cxstring curpos = string;
    2.98 +    while (1) {
    2.99 +        ++n;
   2.100 +        cxstring match = cx_strstr(curpos, delim);
   2.101 +        if (match.length > 0) {
   2.102 +            /* is the limit reached? */
   2.103 +            if (n < limit) {
   2.104 +                size_t processed = match.ptr - curpos.ptr + delim.length;
   2.105 +                curpos.ptr += processed;
   2.106 +                curpos.length -= processed;
   2.107 +            } else {
   2.108 +                /* limit reached */
   2.109 +                break;
   2.110 +            }
   2.111 +        } else {
   2.112 +            /* no more matches */
   2.113 +            break;
   2.114 +        }
   2.115 +    }
   2.116 +    *output = cxCalloc(allocator, n, sizeof(cxstring));
   2.117 +    return cx_strsplit(string, delim, n, *output);
   2.118  }
   2.119  
   2.120  size_t cx_strsplit_m(
   2.121 @@ -344,7 +426,10 @@
   2.122                           delim, limit, (cxstring **) output);
   2.123  }
   2.124  
   2.125 -int cx_strcmp(cxstring s1, cxstring s2) {
   2.126 +int cx_strcmp(
   2.127 +        cxstring s1,
   2.128 +        cxstring s2
   2.129 +) {
   2.130      if (s1.length == s2.length) {
   2.131          return memcmp(s1.ptr, s2.ptr, s1.length);
   2.132      } else if (s1.length > s2.length) {
   2.133 @@ -354,7 +439,10 @@
   2.134      }
   2.135  }
   2.136  
   2.137 -int cx_strcasecmp(cxstring s1, cxstring s2) {
   2.138 +int cx_strcasecmp(
   2.139 +        cxstring s1,
   2.140 +        cxstring s2
   2.141 +) {
   2.142      if (s1.length == s2.length) {
   2.143  #ifdef _WIN32
   2.144          return _strnicmp(s1.ptr, s2.ptr, s1.length);
   2.145 @@ -368,7 +456,10 @@
   2.146      }
   2.147  }
   2.148  
   2.149 -cxmutstr cx_strdup_a(CxAllocator *allocator, cxstring string) {
   2.150 +cxmutstr cx_strdup_a(
   2.151 +        CxAllocator *allocator,
   2.152 +        cxstring string
   2.153 +) {
   2.154      cxmutstr result = {
   2.155              cxMalloc(allocator, string.length + 1),
   2.156              string.length
   2.157 @@ -400,18 +491,27 @@
   2.158      return (cxmutstr) {(char *) result.ptr, result.length};
   2.159  }
   2.160  
   2.161 -bool cx_strprefix(cxstring string, cxstring prefix) {
   2.162 +bool cx_strprefix(
   2.163 +        cxstring string,
   2.164 +        cxstring prefix
   2.165 +) {
   2.166      if (string.length < prefix.length) return false;
   2.167      return memcmp(string.ptr, prefix.ptr, prefix.length) == 0;
   2.168  }
   2.169  
   2.170 -bool cx_strsuffix(cxstring string, cxstring suffix) {
   2.171 +bool cx_strsuffix(
   2.172 +        cxstring string,
   2.173 +        cxstring suffix
   2.174 +) {
   2.175      if (string.length < suffix.length) return false;
   2.176      return memcmp(string.ptr + string.length - suffix.length,
   2.177                    suffix.ptr, suffix.length) == 0;
   2.178  }
   2.179  
   2.180 -bool cx_casestrprefix(cxstring string, cxstring prefix) {
   2.181 +bool cx_strcaseprefix(
   2.182 +        cxstring string,
   2.183 +        cxstring prefix
   2.184 +) {
   2.185      if (string.length < prefix.length) return false;
   2.186  #ifdef _WIN32
   2.187      return _strnicmp(string.ptr, prefix.ptr, prefix.length) == 0;
   2.188 @@ -420,7 +520,10 @@
   2.189  #endif
   2.190  }
   2.191  
   2.192 -bool cx_casestrsuffix(cxstring string, cxstring suffix) {
   2.193 +bool cx_strcasesuffix(
   2.194 +        cxstring string,
   2.195 +        cxstring suffix
   2.196 +) {
   2.197      if (string.length < suffix.length) return false;
   2.198  #ifdef _WIN32
   2.199      return _strnicmp(string.ptr+string.length-suffix.length,
   2.200 @@ -442,3 +545,133 @@
   2.201          string.ptr[i] = toupper(string.ptr[i]);
   2.202      }
   2.203  }
   2.204 +
   2.205 +#define REPLACE_INDEX_BUFFER_MAX 100
   2.206 +
   2.207 +struct cx_strreplace_ibuf {
   2.208 +    size_t *buf;
   2.209 +    unsigned int len; /* small indices */
   2.210 +    struct cx_strreplace_ibuf *next;
   2.211 +};
   2.212 +
   2.213 +static void cx_strrepl_free_ibuf(struct cx_strreplace_ibuf *buf) {
   2.214 +    while (buf) {
   2.215 +        struct cx_strreplace_ibuf *next = buf->next;
   2.216 +        free(buf->buf);
   2.217 +        free(buf);
   2.218 +        buf = next;
   2.219 +    }
   2.220 +}
   2.221 +
   2.222 +cxmutstr cx_strreplacen_a(
   2.223 +        CxAllocator *allocator,
   2.224 +        cxstring str,
   2.225 +        cxstring pattern,
   2.226 +        cxstring replacement,
   2.227 +        size_t replmax
   2.228 +) {
   2.229 +
   2.230 +    if (pattern.length == 0 || pattern.length > str.length || replmax == 0)
   2.231 +        return cx_strdup_a(allocator, str);
   2.232 +
   2.233 +    /* Compute expected buffer length */
   2.234 +    size_t ibufmax = str.length / pattern.length;
   2.235 +    size_t ibuflen = replmax < ibufmax ? replmax : ibufmax;
   2.236 +    if (ibuflen > REPLACE_INDEX_BUFFER_MAX) {
   2.237 +        ibuflen = REPLACE_INDEX_BUFFER_MAX;
   2.238 +    }
   2.239 +
   2.240 +    /* Allocate first index buffer */
   2.241 +    struct cx_strreplace_ibuf *firstbuf, *curbuf;
   2.242 +    firstbuf = curbuf = calloc(1, sizeof(struct cx_strreplace_ibuf));
   2.243 +    if (!firstbuf) return cx_mutstrn(NULL, 0);
   2.244 +    firstbuf->buf = calloc(ibuflen, sizeof(size_t));
   2.245 +    if (!firstbuf->buf) {
   2.246 +        free(firstbuf);
   2.247 +        return cx_mutstrn(NULL, 0);
   2.248 +    }
   2.249 +
   2.250 +    /* Search occurrences */
   2.251 +    cxstring searchstr = str;
   2.252 +    size_t found = 0;
   2.253 +    do {
   2.254 +        cxstring match = cx_strstr(searchstr, pattern);
   2.255 +        if (match.length > 0) {
   2.256 +            /* Allocate next buffer in chain, if required */
   2.257 +            if (curbuf->len == ibuflen) {
   2.258 +                struct cx_strreplace_ibuf *nextbuf =
   2.259 +                        calloc(1, sizeof(struct cx_strreplace_ibuf));
   2.260 +                if (!nextbuf) {
   2.261 +                    cx_strrepl_free_ibuf(firstbuf);
   2.262 +                    return cx_mutstrn(NULL, 0);
   2.263 +                }
   2.264 +                nextbuf->buf = calloc(ibuflen, sizeof(size_t));
   2.265 +                if (!nextbuf->buf) {
   2.266 +                    free(nextbuf);
   2.267 +                    cx_strrepl_free_ibuf(firstbuf);
   2.268 +                    return cx_mutstrn(NULL, 0);
   2.269 +                }
   2.270 +                curbuf->next = nextbuf;
   2.271 +                curbuf = nextbuf;
   2.272 +            }
   2.273 +
   2.274 +            /* Record match index */
   2.275 +            found++;
   2.276 +            size_t idx = match.ptr - str.ptr;
   2.277 +            curbuf->buf[curbuf->len++] = idx;
   2.278 +            searchstr.ptr = match.ptr + pattern.length;
   2.279 +            searchstr.length = str.length - idx - pattern.length;
   2.280 +        } else {
   2.281 +            break;
   2.282 +        }
   2.283 +    } while (searchstr.length > 0 && found < replmax);
   2.284 +
   2.285 +    /* Allocate result string */
   2.286 +    cxmutstr result;
   2.287 +    {
   2.288 +        ssize_t adjlen = (ssize_t) replacement.length - (ssize_t) pattern.length;
   2.289 +        size_t rcount = 0;
   2.290 +        curbuf = firstbuf;
   2.291 +        do {
   2.292 +            rcount += curbuf->len;
   2.293 +            curbuf = curbuf->next;
   2.294 +        } while (curbuf);
   2.295 +        result.length = str.length + rcount * adjlen;
   2.296 +        result.ptr = cxMalloc(allocator, result.length);
   2.297 +        if (!result.ptr) {
   2.298 +            cx_strrepl_free_ibuf(firstbuf);
   2.299 +            return cx_mutstrn(NULL, 0);
   2.300 +        }
   2.301 +    }
   2.302 +
   2.303 +    /* Build result string */
   2.304 +    curbuf = firstbuf;
   2.305 +    size_t srcidx = 0;
   2.306 +    char *destptr = result.ptr;
   2.307 +    do {
   2.308 +        for (size_t i = 0; i < curbuf->len; i++) {
   2.309 +            /* Copy source part up to next match*/
   2.310 +            size_t idx = curbuf->buf[i];
   2.311 +            size_t srclen = idx - srcidx;
   2.312 +            if (srclen > 0) {
   2.313 +                memcpy(destptr, str.ptr + srcidx, srclen);
   2.314 +                destptr += srclen;
   2.315 +                srcidx += srclen;
   2.316 +            }
   2.317 +
   2.318 +            /* Copy the replacement and skip the source pattern */
   2.319 +            srcidx += pattern.length;
   2.320 +            memcpy(destptr, replacement.ptr, replacement.length);
   2.321 +            destptr += replacement.length;
   2.322 +        }
   2.323 +        curbuf = curbuf->next;
   2.324 +    } while (curbuf);
   2.325 +    memcpy(destptr, str.ptr + srcidx, str.length - srcidx);
   2.326 +
   2.327 +    /* Free index buffer */
   2.328 +    cx_strrepl_free_ibuf(firstbuf);
   2.329 +
   2.330 +    return result;
   2.331 +}
   2.332 +
   2.333 +
     3.1 --- a/test/CMakeLists.txt	Sat Sep 03 15:11:23 2022 +0200
     3.2 +++ b/test/CMakeLists.txt	Fri Sep 09 20:19:08 2022 +0200
     3.3 @@ -15,6 +15,7 @@
     3.4  
     3.5  add_executable(ucxtest
     3.6          test_allocator.cpp
     3.7 +        test_string.cpp
     3.8          test_buffer.cpp
     3.9          test_list.cpp
    3.10          test_tree.cpp
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/test/test_string.cpp	Fri Sep 09 20:19:08 2022 +0200
     4.3 @@ -0,0 +1,531 @@
     4.4 +/*
     4.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     4.6 + *
     4.7 + * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
     4.8 + *
     4.9 + * Redistribution and use in source and binary forms, with or without
    4.10 + * modification, are permitted provided that the following conditions are met:
    4.11 + *
    4.12 + *   1. Redistributions of source code must retain the above copyright
    4.13 + *      notice, this list of conditions and the following disclaimer.
    4.14 + *
    4.15 + *   2. Redistributions in binary form must reproduce the above copyright
    4.16 + *      notice, this list of conditions and the following disclaimer in the
    4.17 + *      documentation and/or other materials provided with the distribution.
    4.18 + *
    4.19 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    4.20 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    4.21 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    4.22 + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    4.23 + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    4.24 + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    4.25 + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    4.26 + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    4.27 + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    4.28 + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    4.29 + * POSSIBILITY OF SUCH DAMAGE.
    4.30 + */
    4.31 +
    4.32 +#include "cx/string.h"
    4.33 +#include "util_allocator.h"
    4.34 +
    4.35 +#include <gtest/gtest.h>
    4.36 +
    4.37 +TEST(String, construct) {
    4.38 +    cxstring s1 = cx_str("1234");
    4.39 +    cxstring s2 = cx_strn("abcd", 2);
    4.40 +    cxmutstr s3 = cx_mutstr((char *) "1234");
    4.41 +    cxmutstr s4 = cx_mutstrn((char *) "abcd", 2);
    4.42 +
    4.43 +    EXPECT_EQ(s1.length, 4);
    4.44 +    EXPECT_EQ(s2.length, 2);
    4.45 +    EXPECT_EQ(s3.length, 4);
    4.46 +    EXPECT_EQ(s4.length, 2);
    4.47 +}
    4.48 +
    4.49 +TEST(String, strfree) {
    4.50 +    CxTestingAllocator alloc;
    4.51 +    auto test = (char *) cxMalloc(&alloc, 16);
    4.52 +    cxmutstr str = cx_mutstrn(test, 16);
    4.53 +    ASSERT_EQ(str.ptr, test);
    4.54 +    EXPECT_EQ(str.length, 16);
    4.55 +    cx_strfree_a(&alloc, &str);
    4.56 +    EXPECT_EQ(str.ptr, nullptr);
    4.57 +    EXPECT_EQ(str.length, 0);
    4.58 +    EXPECT_TRUE(alloc.verify());
    4.59 +}
    4.60 +
    4.61 +TEST(String, strlen) {
    4.62 +    cxstring s1 = CX_STR("1234");
    4.63 +    cxstring s2 = CX_STR(".:.:.");
    4.64 +    cxstring s3 = CX_STR("X");
    4.65 +
    4.66 +    size_t len0 = cx_strlen(0);
    4.67 +    size_t len1 = cx_strlen(1, s1);
    4.68 +    size_t len2 = cx_strlen(2, s1, s2);
    4.69 +    size_t len3 = cx_strlen(3, s1, s2, s3);
    4.70 +
    4.71 +    EXPECT_EQ(len0, 0);
    4.72 +    EXPECT_EQ(len1, 4);
    4.73 +    EXPECT_EQ(len2, 9);
    4.74 +    EXPECT_EQ(len3, 10);
    4.75 +}
    4.76 +
    4.77 +
    4.78 +TEST(String, strchr) {
    4.79 +    cxstring str = CX_STR("I will find you - and I will kill you");
    4.80 +
    4.81 +    cxstring notfound = cx_strchr(str, 'x');
    4.82 +    EXPECT_EQ(notfound.length, 0);
    4.83 +
    4.84 +    cxstring result = cx_strchr(str, 'w');
    4.85 +    EXPECT_EQ(result.length, 35);
    4.86 +    EXPECT_EQ(strcmp("will find you - and I will kill you", result.ptr), 0);
    4.87 +}
    4.88 +
    4.89 +TEST(String, strrchr) {
    4.90 +    cxstring str = CX_STR("I will find you - and I will kill you");
    4.91 +
    4.92 +    cxstring notfound = cx_strrchr(str, 'x');
    4.93 +    EXPECT_EQ(notfound.length, 0);
    4.94 +
    4.95 +    cxstring result = cx_strrchr(str, 'w');
    4.96 +    EXPECT_EQ(result.length, 13);
    4.97 +    EXPECT_EQ(strcmp("will kill you", result.ptr), 0);
    4.98 +}
    4.99 +
   4.100 +TEST(String, strstr) {
   4.101 +    cxstring str = CX_STR("find the match in this string");
   4.102 +    cxstring longstr = CX_STR(
   4.103 +            "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl"
   4.104 +            "mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx"
   4.105 +            "yzabcdeababababnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij"
   4.106 +            "klmnopqrstuvwxyzaababababababababrstuvwxyzabcdefghijklmnopqrstuv"
   4.107 +            "abababababababababababababababababababababababababababababababab"
   4.108 +            "abababababababababababababababababababababababababababababababab"
   4.109 +            "abababababababababababababababababababababababababababababababab"
   4.110 +            "abababababababababababababababababababababababababababababababab"
   4.111 +            "abababababababababababababababababababababababababababababababab"
   4.112 +            "abababababababababababababababababababababababababababababababab"
   4.113 +            "wxyz1234567890");
   4.114 +    cxstring longstrpattern = CX_STR(
   4.115 +            "abababababababababababababababababababababababababababababababab"
   4.116 +            "abababababababababababababababababababababababababababababababab"
   4.117 +            "abababababababababababababababababababababababababababababababab"
   4.118 +            "abababababababababababababababababababababababababababababababab"
   4.119 +            "abababababababababababababababababababababababababababababababab"
   4.120 +    );
   4.121 +    cxstring longstrresult = CX_STR(
   4.122 +            "abababababababababababababababababababababababababababababababab"
   4.123 +            "abababababababababababababababababababababababababababababababab"
   4.124 +            "abababababababababababababababababababababababababababababababab"
   4.125 +            "abababababababababababababababababababababababababababababababab"
   4.126 +            "abababababababababababababababababababababababababababababababab"
   4.127 +            "abababababababababababababababababababababababababababababababab"
   4.128 +            "wxyz1234567890"
   4.129 +    );
   4.130 +
   4.131 +    cxstring notfound = cx_strstr(str, cx_str("no match"));
   4.132 +    EXPECT_EQ(notfound.length, 0);
   4.133 +
   4.134 +    cxstring result = cx_strstr(str, cx_str("match"));
   4.135 +    EXPECT_EQ(result.length, 20);
   4.136 +    EXPECT_EQ(strcmp("match in this string", result.ptr), 0);
   4.137 +
   4.138 +    result = cx_strstr(str, cx_str(""));
   4.139 +    EXPECT_EQ(result.length, str.length);
   4.140 +    EXPECT_EQ(strcmp(str.ptr, result.ptr), 0);
   4.141 +
   4.142 +    result = cx_strstr(longstr, longstrpattern);
   4.143 +    EXPECT_EQ(result.length, longstrresult.length);
   4.144 +    EXPECT_EQ(strcmp(result.ptr, longstrresult.ptr), 0);
   4.145 +}
   4.146 +
   4.147 +TEST(String, strcmp) {
   4.148 +    cxstring str = CX_STR("compare this");
   4.149 +
   4.150 +    EXPECT_EQ(cx_strcmp(CX_STR(""), CX_STR("")), 0);
   4.151 +    EXPECT_GT(cx_strcmp(str, CX_STR("")), 0);
   4.152 +    EXPECT_EQ(cx_strcmp(str, CX_STR("compare this")), 0);
   4.153 +    EXPECT_NE(cx_strcmp(str, CX_STR("Compare This")), 0);
   4.154 +    EXPECT_LT(cx_strcmp(str, CX_STR("compare tool")), 0);
   4.155 +    EXPECT_GT(cx_strcmp(str, CX_STR("compare shit")), 0);
   4.156 +    EXPECT_LT(cx_strcmp(str, CX_STR("compare this not")), 0);
   4.157 +    EXPECT_GT(cx_strcmp(str, CX_STR("compare")), 0);
   4.158 +}
   4.159 +
   4.160 +TEST(String, strcasecmp) {
   4.161 +    cxstring str = CX_STR("compare this");
   4.162 +
   4.163 +    EXPECT_EQ(cx_strcasecmp(CX_STR(""), CX_STR("")), 0);
   4.164 +    EXPECT_GT(cx_strcasecmp(str, CX_STR("")), 0);
   4.165 +    EXPECT_EQ(cx_strcasecmp(str, CX_STR("compare this")), 0);
   4.166 +    EXPECT_EQ(cx_strcasecmp(str, CX_STR("Compare This")), 0);
   4.167 +    EXPECT_LT(cx_strcasecmp(str, CX_STR("compare tool")), 0);
   4.168 +    EXPECT_GT(cx_strcasecmp(str, CX_STR("compare shit")), 0);
   4.169 +    EXPECT_LT(cx_strcasecmp(str, CX_STR("compare this not")), 0);
   4.170 +    EXPECT_GT(cx_strcasecmp(str, CX_STR("compare")), 0);
   4.171 +}
   4.172 +
   4.173 +
   4.174 +TEST(String, strcat) {
   4.175 +    cxstring s1 = CX_STR("12");
   4.176 +    cxstring s2 = CX_STR("34");
   4.177 +    cxstring s3 = CX_STR("56");
   4.178 +    cxstring sn = {nullptr, 0};
   4.179 +
   4.180 +    CxTestingAllocator alloc;
   4.181 +
   4.182 +    cxmutstr t1 = cx_strcat_a(&alloc, 2, s1, s2);
   4.183 +    EXPECT_EQ(cx_strcmp(cx_strcast(t1), CX_STR("1234")), 0);
   4.184 +    cx_strfree_a(&alloc, &t1);
   4.185 +
   4.186 +    cxmutstr t2 = cx_strcat_a(&alloc, 3, s1, s2, s3);
   4.187 +    EXPECT_EQ(cx_strcmp(cx_strcast(t2), CX_STR("123456")), 0);
   4.188 +    cx_strfree_a(&alloc, &t2);
   4.189 +
   4.190 +    cxmutstr t3 = cx_strcat_a(&alloc, 6, s1, sn, s2, sn, s3, sn);
   4.191 +    EXPECT_EQ(cx_strcmp(cx_strcast(t3), CX_STR("123456")), 0);
   4.192 +    cx_strfree_a(&alloc, &t3);
   4.193 +
   4.194 +    cxmutstr t4 = cx_strcat_a(&alloc, 2, sn, sn);
   4.195 +    EXPECT_EQ(cx_strcmp(cx_strcast(t4), CX_STR("")), 0);
   4.196 +    cx_strfree_a(&alloc, &t4);
   4.197 +
   4.198 +    EXPECT_TRUE(alloc.verify());
   4.199 +}
   4.200 +
   4.201 +TEST(String, strsplit) {
   4.202 +
   4.203 +    cxstring test = cx_str("this,is,a,csv,string");
   4.204 +    size_t capa = 8;
   4.205 +    cxstring list[8];
   4.206 +    size_t n;
   4.207 +
   4.208 +    /* special case: empty string */
   4.209 +    n = cx_strsplit(test, cx_str(""), capa, list);
   4.210 +    ASSERT_EQ(n, 1);
   4.211 +    EXPECT_EQ(cx_strcmp(list[0], test), 0);
   4.212 +
   4.213 +    /* no delimiter occurrence */
   4.214 +    n = cx_strsplit(test, cx_str("z"), capa, list);
   4.215 +    ASSERT_EQ(n, 1);
   4.216 +    EXPECT_EQ(cx_strcmp(list[0], test), 0);
   4.217 +
   4.218 +    /* partially matching delimiter */
   4.219 +    n = cx_strsplit(test, cx_str("is,not"), capa, list);
   4.220 +    ASSERT_EQ(n, 1);
   4.221 +    EXPECT_EQ(cx_strcmp(list[0], test), 0);
   4.222 +
   4.223 +    /* matching single-char delimiter */
   4.224 +    n = cx_strsplit(test, cx_str(","), capa, list);
   4.225 +    ASSERT_EQ(n, 5);
   4.226 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("this")), 0);
   4.227 +    EXPECT_EQ(cx_strcmp(list[1], cx_str("is")), 0);
   4.228 +    EXPECT_EQ(cx_strcmp(list[2], cx_str("a")), 0);
   4.229 +    EXPECT_EQ(cx_strcmp(list[3], cx_str("csv")), 0);
   4.230 +    EXPECT_EQ(cx_strcmp(list[4], cx_str("string")), 0);
   4.231 +
   4.232 +    /* matching multi-char delimiter */
   4.233 +    n = cx_strsplit(test, cx_str("is"), capa, list);
   4.234 +    ASSERT_EQ(n, 3);
   4.235 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("th")), 0);
   4.236 +    EXPECT_EQ(cx_strcmp(list[1], cx_str(",")), 0);
   4.237 +    EXPECT_EQ(cx_strcmp(list[2], cx_str(",a,csv,string")), 0);
   4.238 +
   4.239 +    /* bounded list using single-char delimiter */
   4.240 +    n = cx_strsplit(test, cx_str(","), 3, list);
   4.241 +    ASSERT_EQ(n, 3);
   4.242 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("this")), 0);
   4.243 +    EXPECT_EQ(cx_strcmp(list[1], cx_str("is")), 0);
   4.244 +    EXPECT_EQ(cx_strcmp(list[2], cx_str("a,csv,string")), 0);
   4.245 +
   4.246 +    /* bounded list using multi-char delimiter */
   4.247 +    n = cx_strsplit(test, cx_str("is"), 2, list);
   4.248 +    ASSERT_EQ(n, 2);
   4.249 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("th")), 0);
   4.250 +    EXPECT_EQ(cx_strcmp(list[1], cx_str(",is,a,csv,string")), 0);
   4.251 +
   4.252 +    /* start with delimiter */
   4.253 +    n = cx_strsplit(test, cx_str("this"), capa, list);
   4.254 +    ASSERT_EQ(n, 2);
   4.255 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("")), 0);
   4.256 +    EXPECT_EQ(cx_strcmp(list[1], cx_str(",is,a,csv,string")), 0);
   4.257 +
   4.258 +    /* end with delimiter */
   4.259 +    n = cx_strsplit(test, cx_str("string"), capa, list);
   4.260 +    ASSERT_EQ(n, 2);
   4.261 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("this,is,a,csv,")), 0);
   4.262 +    EXPECT_EQ(cx_strcmp(list[1], cx_str("")), 0);
   4.263 +
   4.264 +
   4.265 +    /* end with delimiter exceed bound */
   4.266 +    n = cx_strsplit(cx_str("a,b,c,"), cx_str(","), 3, list);
   4.267 +    ASSERT_EQ(n, 3);
   4.268 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("a")), 0);
   4.269 +    EXPECT_EQ(cx_strcmp(list[1], cx_str("b")), 0);
   4.270 +    EXPECT_EQ(cx_strcmp(list[2], cx_str("c,")), 0);
   4.271 +
   4.272 +    /* exact match */
   4.273 +    n = cx_strsplit(test, cx_str("this,is,a,csv,string"), capa, list);
   4.274 +    ASSERT_EQ(n, 2);
   4.275 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("")), 0);
   4.276 +    EXPECT_EQ(cx_strcmp(list[1], cx_str("")), 0);
   4.277 +
   4.278 +    /* string to be split is only substring */
   4.279 +    n = cx_strsplit(test, cx_str("this,is,a,csv,string,with,extension"), capa, list);
   4.280 +    ASSERT_EQ(n, 1);
   4.281 +    EXPECT_EQ(cx_strcmp(list[0], test), 0);
   4.282 +
   4.283 +    /* subsequent encounter of delimiter (the string between is empty) */
   4.284 +    n = cx_strsplit(test, cx_str("is,"), capa, list);
   4.285 +    ASSERT_EQ(n, 3);
   4.286 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("th")), 0);
   4.287 +    EXPECT_EQ(cx_strcmp(list[1], cx_str("")), 0);
   4.288 +    EXPECT_EQ(cx_strcmp(list[2], cx_str("a,csv,string")), 0);
   4.289 +}
   4.290 +
   4.291 +TEST(String, strsplit_a) {
   4.292 +    CxTestingAllocator alloc;
   4.293 +
   4.294 +    cxstring test = cx_str("this,is,a,csv,string");
   4.295 +    size_t capa = 8;
   4.296 +    cxstring *list;
   4.297 +    size_t n;
   4.298 +
   4.299 +    /* special case: empty string */
   4.300 +    n = cx_strsplit_a(&alloc, test, cx_str(""), capa, &list);
   4.301 +    ASSERT_EQ(n, 1);
   4.302 +    EXPECT_EQ(cx_strcmp(list[0], test), 0);
   4.303 +    cxFree(&alloc, list);
   4.304 +
   4.305 +    /* no delimiter occurrence */
   4.306 +    n = cx_strsplit_a(&alloc, test, cx_str("z"), capa, &list);
   4.307 +    ASSERT_EQ(n, 1);
   4.308 +    EXPECT_EQ(cx_strcmp(list[0], test), 0);
   4.309 +    cxFree(&alloc, list);
   4.310 +
   4.311 +    /* partially matching delimiter */
   4.312 +    n = cx_strsplit_a(&alloc, test, cx_str("is,not"), capa, &list);
   4.313 +    ASSERT_EQ(n, 1);
   4.314 +    EXPECT_EQ(cx_strcmp(list[0], test), 0);
   4.315 +    cxFree(&alloc, list);
   4.316 +
   4.317 +    /* matching single-char delimiter */
   4.318 +    n = cx_strsplit_a(&alloc, test, cx_str(","), capa, &list);
   4.319 +    ASSERT_EQ(n, 5);
   4.320 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("this")), 0);
   4.321 +    EXPECT_EQ(cx_strcmp(list[1], cx_str("is")), 0);
   4.322 +    EXPECT_EQ(cx_strcmp(list[2], cx_str("a")), 0);
   4.323 +    EXPECT_EQ(cx_strcmp(list[3], cx_str("csv")), 0);
   4.324 +    EXPECT_EQ(cx_strcmp(list[4], cx_str("string")), 0);
   4.325 +    cxFree(&alloc, list);
   4.326 +
   4.327 +    /* matching multi-char delimiter */
   4.328 +    n = cx_strsplit_a(&alloc, test, cx_str("is"), capa, &list);
   4.329 +    ASSERT_EQ(n, 3);
   4.330 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("th")), 0);
   4.331 +    EXPECT_EQ(cx_strcmp(list[1], cx_str(",")), 0);
   4.332 +    EXPECT_EQ(cx_strcmp(list[2], cx_str(",a,csv,string")), 0);
   4.333 +    cxFree(&alloc, list);
   4.334 +
   4.335 +    /* bounded list using single-char delimiter */
   4.336 +    n = cx_strsplit_a(&alloc, test, cx_str(","), 3, &list);
   4.337 +    ASSERT_EQ(n, 3);
   4.338 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("this")), 0);
   4.339 +    EXPECT_EQ(cx_strcmp(list[1], cx_str("is")), 0);
   4.340 +    EXPECT_EQ(cx_strcmp(list[2], cx_str("a,csv,string")), 0);
   4.341 +    cxFree(&alloc, list);
   4.342 +
   4.343 +    /* bounded list using multi-char delimiter */
   4.344 +    n = cx_strsplit_a(&alloc, test, cx_str("is"), 2, &list);
   4.345 +    ASSERT_EQ(n, 2);
   4.346 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("th")), 0);
   4.347 +    EXPECT_EQ(cx_strcmp(list[1], cx_str(",is,a,csv,string")), 0);
   4.348 +    cxFree(&alloc, list);
   4.349 +
   4.350 +    /* start with delimiter */
   4.351 +    n = cx_strsplit_a(&alloc, test, cx_str("this"), capa, &list);
   4.352 +    ASSERT_EQ(n, 2);
   4.353 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("")), 0);
   4.354 +    EXPECT_EQ(cx_strcmp(list[1], cx_str(",is,a,csv,string")), 0);
   4.355 +    cxFree(&alloc, list);
   4.356 +
   4.357 +    /* end with delimiter */
   4.358 +    n = cx_strsplit_a(&alloc, test, cx_str("string"), capa, &list);
   4.359 +    ASSERT_EQ(n, 2);
   4.360 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("this,is,a,csv,")), 0);
   4.361 +    EXPECT_EQ(cx_strcmp(list[1], cx_str("")), 0);
   4.362 +    cxFree(&alloc, list);
   4.363 +
   4.364 +    /* end with delimiter exceed bound */
   4.365 +    n = cx_strsplit_a(&alloc, cx_str("a,b,c,"), cx_str(","), 3, &list);
   4.366 +    ASSERT_EQ(n, 3);
   4.367 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("a")), 0);
   4.368 +    EXPECT_EQ(cx_strcmp(list[1], cx_str("b")), 0);
   4.369 +    EXPECT_EQ(cx_strcmp(list[2], cx_str("c,")), 0);
   4.370 +    cxFree(&alloc, list);
   4.371 +
   4.372 +    /* exact match */
   4.373 +    n = cx_strsplit_a(&alloc, test, cx_str("this,is,a,csv,string"), capa, &list);
   4.374 +    ASSERT_EQ(n, 2);
   4.375 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("")), 0);
   4.376 +    EXPECT_EQ(cx_strcmp(list[1], cx_str("")), 0);
   4.377 +    cxFree(&alloc, list);
   4.378 +
   4.379 +    /* string to be split is only substring */
   4.380 +    n = cx_strsplit_a(&alloc, test, cx_str("this,is,a,csv,string,with,extension"), capa, &list);
   4.381 +    ASSERT_EQ(n, 1);
   4.382 +    EXPECT_EQ(cx_strcmp(list[0], test), 0);
   4.383 +    cxFree(&alloc, list);
   4.384 +
   4.385 +    /* subsequent encounter of delimiter (the string between is empty) */
   4.386 +    n = cx_strsplit_a(&alloc, test, cx_str("is,"), capa, &list);
   4.387 +    ASSERT_EQ(n, 3);
   4.388 +    EXPECT_EQ(cx_strcmp(list[0], cx_str("th")), 0);
   4.389 +    EXPECT_EQ(cx_strcmp(list[1], cx_str("")), 0);
   4.390 +    EXPECT_EQ(cx_strcmp(list[2], cx_str("a,csv,string")), 0);
   4.391 +    cxFree(&alloc, list);
   4.392 +
   4.393 +    EXPECT_TRUE(alloc.verify());
   4.394 +}
   4.395 +
   4.396 +TEST(String, strtrim) {
   4.397 +    cxstring t1 = cx_strtrim(cx_str("  ein test  \t "));
   4.398 +    cxstring t2 = cx_strtrim(cx_str("abc"));
   4.399 +    cxstring t3 = cx_strtrim(cx_str(" 123"));
   4.400 +    cxstring t4 = cx_strtrim(cx_str("xyz "));
   4.401 +    cxstring t5 = cx_strtrim(cx_str("   "));
   4.402 +    cxstring empty = cx_strtrim(cx_str(""));
   4.403 +
   4.404 +    EXPECT_EQ(cx_strcmp(t1, cx_str("ein test")), 0);
   4.405 +    EXPECT_EQ(cx_strcmp(t2, cx_str("abc")), 0);
   4.406 +    EXPECT_EQ(cx_strcmp(t3, cx_str("123")), 0);
   4.407 +    EXPECT_EQ(cx_strcmp(t4, cx_str("xyz")), 0);
   4.408 +    EXPECT_EQ(cx_strcmp(t5, cx_str("")), 0);
   4.409 +    EXPECT_EQ(cx_strcmp(empty, cx_str("")), 0);
   4.410 +}
   4.411 +
   4.412 +TEST(String, strprefix) {
   4.413 +    cxstring str = CX_STR("test my prefix and my suffix");
   4.414 +    cxstring empty = CX_STR("");
   4.415 +    EXPECT_FALSE(cx_strprefix(empty, cx_str("pref")));
   4.416 +    EXPECT_TRUE(cx_strprefix(str, empty));
   4.417 +    EXPECT_TRUE(cx_strprefix(empty, empty));
   4.418 +    EXPECT_TRUE(cx_strprefix(str, cx_str("test ")));
   4.419 +    EXPECT_FALSE(cx_strprefix(str, cx_str("8-) fsck ")));
   4.420 +}
   4.421 +
   4.422 +TEST(String, strsuffix) {
   4.423 +    cxstring str = CX_STR("test my prefix and my suffix");
   4.424 +    cxstring empty = CX_STR("");
   4.425 +    EXPECT_FALSE(cx_strsuffix(empty, cx_str("suf")));
   4.426 +    EXPECT_TRUE(cx_strsuffix(str, empty));
   4.427 +    EXPECT_TRUE(cx_strsuffix(empty, empty));
   4.428 +    EXPECT_TRUE(cx_strsuffix(str, cx_str("fix")));
   4.429 +    EXPECT_FALSE(cx_strsuffix(str, cx_str("fox")));
   4.430 +}
   4.431 +
   4.432 +TEST(String, strcaseprefix) {
   4.433 +    cxstring str = CX_STR("test my prefix and my suffix");
   4.434 +    cxstring empty = CX_STR("");
   4.435 +    EXPECT_FALSE(cx_strcaseprefix(empty, cx_str("pREf")));
   4.436 +    EXPECT_TRUE(cx_strcaseprefix(str, empty));
   4.437 +    EXPECT_TRUE(cx_strcaseprefix(empty, empty));
   4.438 +    EXPECT_TRUE(cx_strcaseprefix(str, cx_str("TEST ")));
   4.439 +    EXPECT_FALSE(cx_strcaseprefix(str, cx_str("8-) fsck ")));
   4.440 +}
   4.441 +
   4.442 +TEST(String, strcasesuffix) {
   4.443 +    cxstring str = CX_STR("test my prefix and my suffix");
   4.444 +    cxstring empty = CX_STR("");
   4.445 +    EXPECT_FALSE(cx_strcasesuffix(empty, cx_str("sUf")));
   4.446 +    EXPECT_TRUE(cx_strcasesuffix(str, empty));
   4.447 +    EXPECT_TRUE(cx_strcasesuffix(empty, empty));
   4.448 +    EXPECT_TRUE(cx_strcasesuffix(str, cx_str("FIX")));
   4.449 +    EXPECT_FALSE(cx_strcasesuffix(str, cx_str("fox")));
   4.450 +}
   4.451 +
   4.452 +TEST(String, strreplace) {
   4.453 +    cxstring str = CX_STR("test ababab string aba");
   4.454 +    cxstring longstr = CX_STR(
   4.455 +            "xyaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacd");
   4.456 +    cxstring notrail = CX_STR("test abab");
   4.457 +    cxstring empty = CX_STR("");
   4.458 +    cxstring astr = CX_STR("aaaaaaaaaa");
   4.459 +    cxstring csstr = CX_STR("test AB ab TEST xyz");
   4.460 +
   4.461 +    cxmutstr repl = cx_strreplace(str, cx_str("abab"), cx_str("muchlonger"));
   4.462 +    cxstring expected = CX_STR("test muchlongerab string aba");
   4.463 +
   4.464 +    cxmutstr repln = cx_strreplacen(str, cx_str("ab"), cx_str("c"), 2);
   4.465 +    cxstring expectedn = CX_STR("test ccab string aba");
   4.466 +
   4.467 +    cxmutstr longrepl = cx_strreplace(longstr, cx_str("a"), cx_str("z"));
   4.468 +    cxstring longexpect = CX_STR(
   4.469 +            "xyzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzcd");
   4.470 +
   4.471 +    cxmutstr replnotrail = cx_strreplace(notrail, cx_str("ab"), cx_str("z"));
   4.472 +    cxstring notrailexpect = CX_STR("test zz");
   4.473 +
   4.474 +    cxmutstr repleq = cx_strreplace(str, str, cx_str("hello"));
   4.475 +    cxstring eqexpect = CX_STR("hello");
   4.476 +
   4.477 +    cxmutstr replempty1 = cx_strreplace(empty, cx_str("ab"), cx_str("c")); // expect: empty
   4.478 +    cxmutstr replempty2 = cx_strreplace(str, cx_str("abab"), empty);
   4.479 +    cxstring emptyexpect2 = CX_STR("test ab string aba");
   4.480 +
   4.481 +    cxmutstr replpre = cx_strreplace(str, cx_str("test "), cx_str("TEST "));
   4.482 +    cxstring preexpected = CX_STR("TEST ababab string aba");
   4.483 +
   4.484 +    cxmutstr replan1 = cx_strreplacen(astr, cx_str("a"), cx_str("x"), 1);
   4.485 +    cxstring an1expected = CX_STR("xaaaaaaaaa");
   4.486 +
   4.487 +    cxmutstr replan4 = cx_strreplacen(astr, cx_str("a"), cx_str("x"), 4);
   4.488 +    cxstring an4expected = CX_STR("xxxxaaaaaa");
   4.489 +
   4.490 +    cxmutstr replan9 = cx_strreplacen(astr, cx_str("a"), cx_str("x"), 9);
   4.491 +    cxstring an9expected = CX_STR("xxxxxxxxxa");
   4.492 +
   4.493 +    cxmutstr replan10 = cx_strreplacen(astr, cx_str("a"), cx_str("x"), 10);
   4.494 +    cxstring an10expected = CX_STR("xxxxxxxxxx");
   4.495 +
   4.496 +    cxmutstr replcs1 = cx_strreplace(csstr, cx_str("AB"), cx_str("*"));
   4.497 +    cxstring cs1expected = CX_STR("test * ab TEST xyz");
   4.498 +
   4.499 +    cxmutstr replcs2 = cx_strreplace(csstr, cx_str("test"), cx_str("TEST"));
   4.500 +    cxstring cs2expected = CX_STR("TEST AB ab TEST xyz");
   4.501 +
   4.502 +
   4.503 +    EXPECT_NE(repl.ptr, str.ptr);
   4.504 +    EXPECT_EQ(cx_strcmp(cx_strcast(repl), expected), 0);
   4.505 +    EXPECT_NE(repln.ptr, str.ptr);
   4.506 +    EXPECT_EQ(cx_strcmp(cx_strcast(repln), expectedn), 0);
   4.507 +    EXPECT_EQ(cx_strcmp(cx_strcast(longrepl), longexpect), 0);
   4.508 +    EXPECT_EQ(cx_strcmp(cx_strcast(replnotrail), notrailexpect), 0);
   4.509 +    EXPECT_EQ(cx_strcmp(cx_strcast(repleq), eqexpect), 0);
   4.510 +    EXPECT_EQ(cx_strcmp(cx_strcast(replempty1), empty), 0);
   4.511 +    EXPECT_EQ(cx_strcmp(cx_strcast(replempty2), emptyexpect2), 0);
   4.512 +    EXPECT_EQ(cx_strcmp(cx_strcast(replpre), preexpected), 0);
   4.513 +    EXPECT_EQ(cx_strcmp(cx_strcast(replan1), an1expected), 0);
   4.514 +    EXPECT_EQ(cx_strcmp(cx_strcast(replan4), an4expected), 0);
   4.515 +    EXPECT_EQ(cx_strcmp(cx_strcast(replan9), an9expected), 0);
   4.516 +    EXPECT_EQ(cx_strcmp(cx_strcast(replan10), an10expected), 0);
   4.517 +    EXPECT_EQ(cx_strcmp(cx_strcast(replcs1), cs1expected), 0);
   4.518 +    EXPECT_EQ(cx_strcmp(cx_strcast(replcs2), cs2expected), 0);
   4.519 +
   4.520 +    cx_strfree(&repl);
   4.521 +    cx_strfree(&repln);
   4.522 +    cx_strfree(&longrepl);
   4.523 +    cx_strfree(&replnotrail);
   4.524 +    cx_strfree(&repleq);
   4.525 +    cx_strfree(&replempty1);
   4.526 +    cx_strfree(&replempty2);
   4.527 +    cx_strfree(&replpre);
   4.528 +    cx_strfree(&replan1);
   4.529 +    cx_strfree(&replan4);
   4.530 +    cx_strfree(&replan9);
   4.531 +    cx_strfree(&replan10);
   4.532 +    cx_strfree(&replcs1);
   4.533 +    cx_strfree(&replcs2);
   4.534 +}

mercurial