test/test_string.cpp

Tue, 04 Oct 2022 19:25:07 +0200

author
Mike Becker <universe@uap-core.de>
date
Tue, 04 Oct 2022 19:25:07 +0200
changeset 591
7df0bcaecffa
parent 589
c290f8fd979e
child 597
8b48126671cf
permissions
-rw-r--r--

fix over-optimization of strstr

1. it's actually less performant to frequently read bytes
from an array instead of using the native word length
2. the SBO buffer should be local and not static to allow
multi-threading usage

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "cx/string.h"
#include "util_allocator.h"

#include <gtest/gtest.h>

#define EXPECT_ZERO_TERMINATED(str) EXPECT_EQ((str).ptr[(str).length], '\0')

TEST(String, construct) {
    cxstring s1 = cx_str("1234");
    cxstring s2 = cx_strn("abcd", 2);
    cxmutstr s3 = cx_mutstr((char *) "1234");
    cxmutstr s4 = cx_mutstrn((char *) "abcd", 2);

    EXPECT_EQ(s1.length, 4);
    EXPECT_EQ(s2.length, 2);
    EXPECT_EQ(s3.length, 4);
    EXPECT_EQ(s4.length, 2);
}

TEST(String, strfree) {
    CxTestingAllocator alloc;
    auto test = (char *) cxMalloc(&alloc, 16);
    cxmutstr str = cx_mutstrn(test, 16);
    ASSERT_EQ(str.ptr, test);
    EXPECT_EQ(str.length, 16);
    cx_strfree_a(&alloc, &str);
    EXPECT_EQ(str.ptr, nullptr);
    EXPECT_EQ(str.length, 0);
    EXPECT_TRUE(alloc.verify());
}

TEST(String, strdup) {
    cxstring str = CX_STR("test");
    cxmutstr dup = cx_strdup(str);
    ASSERT_EQ(dup.length, str.length);
    EXPECT_STREQ(dup.ptr, str.ptr);
    EXPECT_ZERO_TERMINATED(dup);
    cx_strfree(&dup);

    str.length = 2;
    dup = cx_strdup(str);
    ASSERT_EQ(dup.length, str.length);
    EXPECT_STREQ(dup.ptr, "te");
    EXPECT_ZERO_TERMINATED(dup);
    cx_strfree(&dup);
}

TEST(String, strlen) {
    cxstring s1 = CX_STR("1234");
    cxstring s2 = CX_STR(".:.:.");
    cxstring s3 = CX_STR("X");

    size_t len0 = cx_strlen(0);
    size_t len1 = cx_strlen(1, s1);
    size_t len2 = cx_strlen(2, s1, s2);
    size_t len3 = cx_strlen(3, s1, s2, s3);

    EXPECT_EQ(len0, 0);
    EXPECT_EQ(len1, 4);
    EXPECT_EQ(len2, 9);
    EXPECT_EQ(len3, 10);
}

TEST(String, strsubs) {
    cxstring str = CX_STR("A test string");

    cxstring sub = cx_strsubs(str, 0);
    EXPECT_EQ(cx_strcmp(sub, str), 0);

    sub = cx_strsubs(str, 2);
    EXPECT_EQ(cx_strcmp(sub, cx_str("test string")), 0);

    sub = cx_strsubs(str, 7);
    EXPECT_EQ(cx_strcmp(sub, cx_str("string")), 0);

    sub = cx_strsubs(str, 15);
    EXPECT_EQ(cx_strcmp(sub, cx_str("")), 0);

    sub = cx_strsubsl(str, 2, 4);
    EXPECT_EQ(cx_strcmp(sub, cx_str("test")), 0);

    sub = cx_strsubsl(str, 7, 3);
    EXPECT_EQ(cx_strcmp(sub, cx_str("str")), 0);

    sub = cx_strsubsl(str, 7, 20);
    EXPECT_EQ(cx_strcmp(sub, cx_str("string")), 0);

    // just for coverage, call the _m variant
    auto m = cx_strsubs_m(cx_mutstrn(nullptr, 0), 0);
    EXPECT_EQ(cx_strcmp(cx_strcast(m), cx_str("")), 0);
}

TEST(String, strchr) {
    cxstring str = CX_STR("I will find you - and I will kill you");

    cxstring notfound = cx_strchr(str, 'x');
    EXPECT_EQ(notfound.length, 0);

    cxstring result = cx_strchr(str, 'w');
    EXPECT_EQ(result.length, 35);
    EXPECT_STREQ(result.ptr, "will find you - and I will kill you");

    // just for coverage, call the _m variant
    auto m = cx_strchr_m(cx_mutstrn(nullptr, 0), 'a');
    EXPECT_EQ(cx_strcmp(cx_strcast(m), cx_str("")), 0);
}

TEST(String, strrchr) {
    cxstring str = CX_STR("I will find you - and I will kill you");

    cxstring notfound = cx_strrchr(str, 'x');
    EXPECT_EQ(notfound.length, 0);

    cxstring result = cx_strrchr(str, 'w');
    EXPECT_EQ(result.length, 13);
    EXPECT_STREQ(result.ptr, "will kill you");

    // just for coverage, call the _m variant
    auto m = cx_strrchr_m(cx_mutstrn(nullptr, 0), 'a');
    EXPECT_EQ(cx_strcmp(cx_strcast(m), cx_str("")), 0);
}

TEST(String, strstr) {
    cxstring str = CX_STR("find the match in this string");
    cxstring longstr = CX_STR(
            "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijkl"
            "mnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwx"
            "yzabcdeababababnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij"
            "klmnopqrstuvwxyzaababababababababrstuvwxyzabcdefghijklmnopqrstuv"
            "abababababababababababababababababababababababababababababababab"
            "abababababababababababababababababababababababababababababababab"
            "abababababababababababababababababababababababababababababababab"
            "abababababababababababababababababababababababababababababababab"
            "abababababababababababababababababababababababababababababababab"
            "abababababababababababababababababababababababababababababababab"
            "wxyz1234567890");
    cxstring longstrpattern = CX_STR(
            "abababababababababababababababababababababababababababababababab"
            "abababababababababababababababababababababababababababababababab"
            "abababababababababababababababababababababababababababababababab"
            "abababababababababababababababababababababababababababababababab"
            "abababababababababababababababababababababababababababababababab"
    );
    cxstring longstrresult = CX_STR(
            "abababababababababababababababababababababababababababababababab"
            "abababababababababababababababababababababababababababababababab"
            "abababababababababababababababababababababababababababababababab"
            "abababababababababababababababababababababababababababababababab"
            "abababababababababababababababababababababababababababababababab"
            "abababababababababababababababababababababababababababababababab"
            "wxyz1234567890"
    );

    cxstring notfound = cx_strstr(str, cx_str("no match"));
    EXPECT_EQ(notfound.length, 0);

    cxstring result = cx_strstr(str, cx_str("match"));
    EXPECT_EQ(result.length, 20);
    EXPECT_STREQ(result.ptr, "match in this string");

    result = cx_strstr(str, cx_str(""));
    EXPECT_EQ(result.length, str.length);
    EXPECT_STREQ(result.ptr, str.ptr);

    result = cx_strstr(longstr, longstrpattern);
    EXPECT_EQ(result.length, longstrresult.length);
    EXPECT_STREQ(result.ptr, longstrresult.ptr);

    // just for coverage, call the _m variant
    auto mstr = cx_strdup(longstr);
    auto m = cx_strstr_m(mstr, longstrpattern);
    EXPECT_EQ(m.length, longstrresult.length);
    EXPECT_STREQ(m.ptr, longstrresult.ptr);
    cx_strfree(&mstr);
}

TEST(String, strcmp) {
    cxstring str = CX_STR("compare this");

    EXPECT_EQ(cx_strcmp(cx_str(""), cx_str("")), 0);
    EXPECT_GT(cx_strcmp(str, cx_str("")), 0);
    EXPECT_EQ(cx_strcmp(str, cx_str("compare this")), 0);
    EXPECT_NE(cx_strcmp(str, cx_str("Compare This")), 0);
    EXPECT_LT(cx_strcmp(str, cx_str("compare tool")), 0);
    EXPECT_GT(cx_strcmp(str, cx_str("compare shit")), 0);
    EXPECT_LT(cx_strcmp(str, cx_str("compare this not")), 0);
    EXPECT_GT(cx_strcmp(str, cx_str("compare")), 0);
}

TEST(String, strcasecmp) {
    cxstring str = CX_STR("compare this");

    EXPECT_EQ(cx_strcasecmp(cx_str(""), cx_str("")), 0);
    EXPECT_GT(cx_strcasecmp(str, cx_str("")), 0);
    EXPECT_EQ(cx_strcasecmp(str, cx_str("compare this")), 0);
    EXPECT_EQ(cx_strcasecmp(str, cx_str("Compare This")), 0);
    EXPECT_LT(cx_strcasecmp(str, cx_str("compare tool")), 0);
    EXPECT_GT(cx_strcasecmp(str, cx_str("compare shit")), 0);
    EXPECT_LT(cx_strcasecmp(str, cx_str("compare this not")), 0);
    EXPECT_GT(cx_strcasecmp(str, cx_str("compare")), 0);
}

TEST(String, strcat) {
    cxstring s1 = CX_STR("12");
    cxstring s2 = CX_STR("34");
    cxstring s3 = CX_STR("56");
    cxstring sn = {nullptr, 0};

    CxTestingAllocator alloc;

    cxmutstr t1 = cx_strcat_a(&alloc, 2, s1, s2);
    EXPECT_EQ(cx_strcmp(cx_strcast(t1), cx_str("1234")), 0);
    EXPECT_ZERO_TERMINATED(t1);
    cx_strfree_a(&alloc, &t1);

    cxmutstr t2 = cx_strcat_a(&alloc, 3, s1, s2, s3);
    EXPECT_EQ(cx_strcmp(cx_strcast(t2), cx_str("123456")), 0);
    EXPECT_ZERO_TERMINATED(t2);
    cx_strfree_a(&alloc, &t2);

    cxmutstr t3 = cx_strcat_a(&alloc, 6, s1, sn, s2, sn, s3, sn);
    EXPECT_EQ(cx_strcmp(cx_strcast(t3), cx_str("123456")), 0);
    EXPECT_ZERO_TERMINATED(t3);
    cx_strfree_a(&alloc, &t3);

    cxmutstr t4 = cx_strcat_a(&alloc, 2, sn, sn);
    EXPECT_EQ(cx_strcmp(cx_strcast(t4), cx_str("")), 0);
    EXPECT_ZERO_TERMINATED(t4);
    cx_strfree_a(&alloc, &t4);

    EXPECT_TRUE(alloc.verify());
}

TEST(String, strsplit) {

    cxstring test = cx_str("this,is,a,csv,string");
    size_t capa = 8;
    cxstring list[8];
    size_t n;

    /* special case: empty string */
    n = cx_strsplit(test, cx_str(""), capa, list);
    ASSERT_EQ(n, 1);
    EXPECT_EQ(cx_strcmp(list[0], test), 0);

    /* no delimiter occurrence */
    n = cx_strsplit(test, cx_str("z"), capa, list);
    ASSERT_EQ(n, 1);
    EXPECT_EQ(cx_strcmp(list[0], test), 0);

    /* partially matching delimiter */
    n = cx_strsplit(test, cx_str("is,not"), capa, list);
    ASSERT_EQ(n, 1);
    EXPECT_EQ(cx_strcmp(list[0], test), 0);

    /* matching single-char delimiter */
    n = cx_strsplit(test, cx_str(","), capa, list);
    ASSERT_EQ(n, 5);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("this")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str("is")), 0);
    EXPECT_EQ(cx_strcmp(list[2], cx_str("a")), 0);
    EXPECT_EQ(cx_strcmp(list[3], cx_str("csv")), 0);
    EXPECT_EQ(cx_strcmp(list[4], cx_str("string")), 0);

    /* matching multi-char delimiter */
    n = cx_strsplit(test, cx_str("is"), capa, list);
    ASSERT_EQ(n, 3);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("th")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str(",")), 0);
    EXPECT_EQ(cx_strcmp(list[2], cx_str(",a,csv,string")), 0);

    /* bounded list using single-char delimiter */
    n = cx_strsplit(test, cx_str(","), 3, list);
    ASSERT_EQ(n, 3);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("this")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str("is")), 0);
    EXPECT_EQ(cx_strcmp(list[2], cx_str("a,csv,string")), 0);

    /* bounded list using multi-char delimiter */
    n = cx_strsplit(test, cx_str("is"), 2, list);
    ASSERT_EQ(n, 2);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("th")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str(",is,a,csv,string")), 0);

    /* start with delimiter */
    n = cx_strsplit(test, cx_str("this"), capa, list);
    ASSERT_EQ(n, 2);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str(",is,a,csv,string")), 0);

    /* end with delimiter */
    n = cx_strsplit(test, cx_str("string"), capa, list);
    ASSERT_EQ(n, 2);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("this,is,a,csv,")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str("")), 0);


    /* end with delimiter exceed bound */
    n = cx_strsplit(cx_str("a,b,c,"), cx_str(","), 3, list);
    ASSERT_EQ(n, 3);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("a")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str("b")), 0);
    EXPECT_EQ(cx_strcmp(list[2], cx_str("c,")), 0);

    /* exact match */
    n = cx_strsplit(test, cx_str("this,is,a,csv,string"), capa, list);
    ASSERT_EQ(n, 2);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str("")), 0);

    /* string to be split is only substring */
    n = cx_strsplit(test, cx_str("this,is,a,csv,string,with,extension"), capa, list);
    ASSERT_EQ(n, 1);
    EXPECT_EQ(cx_strcmp(list[0], test), 0);

    /* subsequent encounter of delimiter (the string between is empty) */
    n = cx_strsplit(test, cx_str("is,"), capa, list);
    ASSERT_EQ(n, 3);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("th")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str("")), 0);
    EXPECT_EQ(cx_strcmp(list[2], cx_str("a,csv,string")), 0);

    /* call the _m variant just for coverage */
    auto mtest = cx_strdup(test);
    cxmutstr mlist[4];
    n = cx_strsplit_m(mtest, cx_str("is,"), 4, mlist);
    ASSERT_EQ(n, 3);
    EXPECT_EQ(cx_strcmp(cx_strcast(mlist[0]), cx_str("th")), 0);
    EXPECT_EQ(cx_strcmp(cx_strcast(mlist[1]), cx_str("")), 0);
    EXPECT_EQ(cx_strcmp(cx_strcast(mlist[2]), cx_str("a,csv,string")), 0);
    cx_strfree(&mtest);
}

TEST(String, strsplit_a) {
    CxTestingAllocator alloc;

    cxstring test = cx_str("this,is,a,csv,string");
    size_t capa = 8;
    cxstring *list;
    size_t n;

    /* special case: empty string */
    n = cx_strsplit_a(&alloc, test, cx_str(""), capa, &list);
    ASSERT_EQ(n, 1);
    EXPECT_EQ(cx_strcmp(list[0], test), 0);
    cxFree(&alloc, list);

    /* no delimiter occurrence */
    n = cx_strsplit_a(&alloc, test, cx_str("z"), capa, &list);
    ASSERT_EQ(n, 1);
    EXPECT_EQ(cx_strcmp(list[0], test), 0);
    cxFree(&alloc, list);

    /* partially matching delimiter */
    n = cx_strsplit_a(&alloc, test, cx_str("is,not"), capa, &list);
    ASSERT_EQ(n, 1);
    EXPECT_EQ(cx_strcmp(list[0], test), 0);
    cxFree(&alloc, list);

    /* matching single-char delimiter */
    n = cx_strsplit_a(&alloc, test, cx_str(","), capa, &list);
    ASSERT_EQ(n, 5);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("this")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str("is")), 0);
    EXPECT_EQ(cx_strcmp(list[2], cx_str("a")), 0);
    EXPECT_EQ(cx_strcmp(list[3], cx_str("csv")), 0);
    EXPECT_EQ(cx_strcmp(list[4], cx_str("string")), 0);
    cxFree(&alloc, list);

    /* matching multi-char delimiter */
    n = cx_strsplit_a(&alloc, test, cx_str("is"), capa, &list);
    ASSERT_EQ(n, 3);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("th")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str(",")), 0);
    EXPECT_EQ(cx_strcmp(list[2], cx_str(",a,csv,string")), 0);
    cxFree(&alloc, list);

    /* bounded list using single-char delimiter */
    n = cx_strsplit_a(&alloc, test, cx_str(","), 3, &list);
    ASSERT_EQ(n, 3);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("this")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str("is")), 0);
    EXPECT_EQ(cx_strcmp(list[2], cx_str("a,csv,string")), 0);
    cxFree(&alloc, list);

    /* bounded list using multi-char delimiter */
    n = cx_strsplit_a(&alloc, test, cx_str("is"), 2, &list);
    ASSERT_EQ(n, 2);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("th")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str(",is,a,csv,string")), 0);
    cxFree(&alloc, list);

    /* start with delimiter */
    n = cx_strsplit_a(&alloc, test, cx_str("this"), capa, &list);
    ASSERT_EQ(n, 2);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str(",is,a,csv,string")), 0);
    cxFree(&alloc, list);

    /* end with delimiter */
    n = cx_strsplit_a(&alloc, test, cx_str("string"), capa, &list);
    ASSERT_EQ(n, 2);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("this,is,a,csv,")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str("")), 0);
    cxFree(&alloc, list);

    /* end with delimiter exceed bound */
    n = cx_strsplit_a(&alloc, cx_str("a,b,c,"), cx_str(","), 3, &list);
    ASSERT_EQ(n, 3);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("a")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str("b")), 0);
    EXPECT_EQ(cx_strcmp(list[2], cx_str("c,")), 0);
    cxFree(&alloc, list);

    /* exact match */
    n = cx_strsplit_a(&alloc, test, cx_str("this,is,a,csv,string"), capa, &list);
    ASSERT_EQ(n, 2);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str("")), 0);
    cxFree(&alloc, list);

    /* string to be split is only substring */
    n = cx_strsplit_a(&alloc, test, cx_str("this,is,a,csv,string,with,extension"), capa, &list);
    ASSERT_EQ(n, 1);
    EXPECT_EQ(cx_strcmp(list[0], test), 0);
    cxFree(&alloc, list);

    /* subsequent encounter of delimiter (the string between is empty) */
    n = cx_strsplit_a(&alloc, test, cx_str("is,"), capa, &list);
    ASSERT_EQ(n, 3);
    EXPECT_EQ(cx_strcmp(list[0], cx_str("th")), 0);
    EXPECT_EQ(cx_strcmp(list[1], cx_str("")), 0);
    EXPECT_EQ(cx_strcmp(list[2], cx_str("a,csv,string")), 0);
    cxFree(&alloc, list);

    /* call the _m variant just for coverage */
    auto mtest = cx_strdup(test);
    cxmutstr *mlist;
    n = cx_strsplit_ma(&alloc, mtest, cx_str("is,"), 4, &mlist);
    ASSERT_EQ(n, 3);
    EXPECT_EQ(cx_strcmp(cx_strcast(mlist[0]), cx_str("th")), 0);
    EXPECT_EQ(cx_strcmp(cx_strcast(mlist[1]), cx_str("")), 0);
    EXPECT_EQ(cx_strcmp(cx_strcast(mlist[2]), cx_str("a,csv,string")), 0);
    cxFree(&alloc, mlist);
    cx_strfree(&mtest);

    EXPECT_TRUE(alloc.verify());
}

TEST(String, strtrim) {
    cxstring t1 = cx_strtrim(cx_str("  ein test  \t "));
    cxstring t2 = cx_strtrim(cx_str("abc"));
    cxstring t3 = cx_strtrim(cx_str(" 123"));
    cxstring t4 = cx_strtrim(cx_str("xyz "));
    cxstring t5 = cx_strtrim(cx_str("   "));
    cxstring empty = cx_strtrim(cx_str(""));

    EXPECT_EQ(cx_strcmp(t1, cx_str("ein test")), 0);
    EXPECT_EQ(cx_strcmp(t2, cx_str("abc")), 0);
    EXPECT_EQ(cx_strcmp(t3, cx_str("123")), 0);
    EXPECT_EQ(cx_strcmp(t4, cx_str("xyz")), 0);
    EXPECT_EQ(cx_strcmp(t5, cx_str("")), 0);
    EXPECT_EQ(cx_strcmp(empty, cx_str("")), 0);

    /* call the _m variant just for coverage */
    cxmutstr m1 = cx_strtrim_m(cx_mutstr((char *) "  ein test  \t "));
    EXPECT_EQ(cx_strcmp(cx_strcast(m1), cx_str("ein test")), 0);
}

TEST(String, strprefix) {
    cxstring str = CX_STR("test my prefix and my suffix");
    cxstring empty = CX_STR("");
    EXPECT_FALSE(cx_strprefix(empty, cx_str("pref")));
    EXPECT_TRUE(cx_strprefix(str, empty));
    EXPECT_TRUE(cx_strprefix(empty, empty));
    EXPECT_TRUE(cx_strprefix(str, cx_str("test ")));
    EXPECT_FALSE(cx_strprefix(str, cx_str("8-) fsck ")));
}

TEST(String, strsuffix) {
    cxstring str = CX_STR("test my prefix and my suffix");
    cxstring empty = CX_STR("");
    EXPECT_FALSE(cx_strsuffix(empty, cx_str("suf")));
    EXPECT_TRUE(cx_strsuffix(str, empty));
    EXPECT_TRUE(cx_strsuffix(empty, empty));
    EXPECT_TRUE(cx_strsuffix(str, cx_str("fix")));
    EXPECT_FALSE(cx_strsuffix(str, cx_str("fox")));
}

TEST(String, strcaseprefix) {
    cxstring str = CX_STR("test my prefix and my suffix");
    cxstring empty = CX_STR("");
    EXPECT_FALSE(cx_strcaseprefix(empty, cx_str("pREf")));
    EXPECT_TRUE(cx_strcaseprefix(str, empty));
    EXPECT_TRUE(cx_strcaseprefix(empty, empty));
    EXPECT_TRUE(cx_strcaseprefix(str, cx_str("TEST ")));
    EXPECT_FALSE(cx_strcaseprefix(str, cx_str("8-) fsck ")));
}

TEST(String, strcasesuffix) {
    cxstring str = CX_STR("test my prefix and my suffix");
    cxstring empty = CX_STR("");
    EXPECT_FALSE(cx_strcasesuffix(empty, cx_str("sUf")));
    EXPECT_TRUE(cx_strcasesuffix(str, empty));
    EXPECT_TRUE(cx_strcasesuffix(empty, empty));
    EXPECT_TRUE(cx_strcasesuffix(str, cx_str("FIX")));
    EXPECT_FALSE(cx_strcasesuffix(str, cx_str("fox")));
}

TEST(String, strreplace) {
    cxstring str = CX_STR("test ababab string aba");
    cxstring longstr = CX_STR(
            "xyaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaacd");
    cxstring notrail = CX_STR("test abab");
    cxstring empty = CX_STR("");
    cxstring astr = CX_STR("aaaaaaaaaa");
    cxstring csstr = CX_STR("test AB ab TEST xyz");

    cxmutstr repl = cx_strreplace(str, cx_str("abab"), cx_str("muchlonger"));
    auto expected = "test muchlongerab string aba";

    cxmutstr repln = cx_strreplacen(str, cx_str("ab"), cx_str("c"), 2);
    auto expectedn = "test ccab string aba";

    cxmutstr longrepl = cx_strreplace(longstr, cx_str("a"), cx_str("z"));
    auto longexpect = "xyzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzcd";

    cxmutstr replnotrail = cx_strreplace(notrail, cx_str("ab"), cx_str("z"));
    auto notrailexpect = "test zz";

    cxmutstr repleq = cx_strreplace(str, str, cx_str("hello"));
    auto eqexpect = "hello";

    cxmutstr replempty1 = cx_strreplace(empty, cx_str("ab"), cx_str("c")); // expect: empty
    cxmutstr replempty2 = cx_strreplace(str, cx_str("abab"), empty);
    auto emptyexpect2 = "test ab string aba";

    cxmutstr replpre = cx_strreplace(str, cx_str("test "), cx_str("TEST "));
    auto preexpected = "TEST ababab string aba";

    cxmutstr replan1 = cx_strreplacen(astr, cx_str("a"), cx_str("x"), 1);
    auto an1expected = "xaaaaaaaaa";

    cxmutstr replan4 = cx_strreplacen(astr, cx_str("a"), cx_str("x"), 4);
    auto an4expected = "xxxxaaaaaa";

    cxmutstr replan9 = cx_strreplacen(astr, cx_str("a"), cx_str("x"), 9);
    auto an9expected = "xxxxxxxxxa";

    cxmutstr replan10 = cx_strreplacen(astr, cx_str("a"), cx_str("x"), 10);
    auto an10expected = "xxxxxxxxxx";

    cxmutstr replcs1 = cx_strreplace(csstr, cx_str("AB"), cx_str("*"));
    auto cs1expected = "test * ab TEST xyz";

    cxmutstr replcs2 = cx_strreplace(csstr, cx_str("test"), cx_str("TEST"));
    auto cs2expected = "TEST AB ab TEST xyz";


    EXPECT_NE(repl.ptr, str.ptr);
    EXPECT_ZERO_TERMINATED(repl);
    EXPECT_STREQ(repl.ptr, expected);
    EXPECT_ZERO_TERMINATED(repln);
    EXPECT_STREQ(repln.ptr, expectedn);
    EXPECT_ZERO_TERMINATED(longrepl);
    EXPECT_STREQ(longrepl.ptr, longexpect);
    EXPECT_ZERO_TERMINATED(replnotrail);
    EXPECT_STREQ(replnotrail.ptr, notrailexpect);
    EXPECT_ZERO_TERMINATED(repleq);
    EXPECT_STREQ(repleq.ptr, eqexpect);
    EXPECT_ZERO_TERMINATED(replempty1);
    EXPECT_STREQ(replempty1.ptr, "");
    EXPECT_ZERO_TERMINATED(replempty2);
    EXPECT_STREQ(replempty2.ptr, emptyexpect2);
    EXPECT_ZERO_TERMINATED(replpre);
    EXPECT_STREQ(replpre.ptr, preexpected);
    EXPECT_ZERO_TERMINATED(replan1);
    EXPECT_STREQ(replan1.ptr, an1expected);
    EXPECT_ZERO_TERMINATED(replan4);
    EXPECT_STREQ(replan4.ptr, an4expected);
    EXPECT_ZERO_TERMINATED(replan9);
    EXPECT_STREQ(replan9.ptr, an9expected);
    EXPECT_ZERO_TERMINATED(replan10);
    EXPECT_STREQ(replan10.ptr, an10expected);
    EXPECT_ZERO_TERMINATED(replcs1);
    EXPECT_STREQ(replcs1.ptr, cs1expected);
    EXPECT_ZERO_TERMINATED(replcs2);
    EXPECT_STREQ(replcs2.ptr, cs2expected);

    cx_strfree(&repl);
    cx_strfree(&repln);
    cx_strfree(&longrepl);
    cx_strfree(&replnotrail);
    cx_strfree(&repleq);
    cx_strfree(&replempty1);
    cx_strfree(&replempty2);
    cx_strfree(&replpre);
    cx_strfree(&replan1);
    cx_strfree(&replan4);
    cx_strfree(&replan9);
    cx_strfree(&replan10);
    cx_strfree(&replcs1);
    cx_strfree(&replcs2);
}

TEST(String, strupper) {
    cxmutstr str = cx_strdup(cx_str("thIs 1s @ Te$t"));
    cx_strupper(str);
    EXPECT_STREQ(str.ptr, "THIS 1S @ TE$T");
    cx_strfree(&str);
}

TEST(String, strlower) {
    cxmutstr str = cx_strdup(cx_str("thIs 1s @ Te$t"));
    cx_strlower(str);
    EXPECT_STREQ(str.ptr, "this 1s @ te$t");
    cx_strfree(&str);
}

mercurial