libei/src/util-strings.c
Peter Hutterer b064d435ec test: make a separate binary for the utils tests
And add the tests for the list and string helpers as a first use-case, copied
from the libinput tests.

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2020-08-25 11:37:18 +10:00

380 lines
9.1 KiB
C

/*
* Copyright © 2008 Kristian Høgsberg
* Copyright © 2013-2015 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "config.h"
#include "util-strings.h"
/**
* Return the next word in a string pointed to by state before the first
* separator character. Call repeatedly to tokenize a whole string.
*
* @param state Current state
* @param len String length of the word returned
* @param separators List of separator characters
*
* @return The first word in *state, NOT null-terminated
*/
static const char *
next_word(const char **state, size_t *len, const char *separators)
{
const char *next = *state;
size_t l;
if (!*next)
return NULL;
next += strspn(next, separators);
if (!*next) {
*state = next;
return NULL;
}
l = strcspn(next, separators);
*state = next + l;
*len = l;
return next;
}
/**
* Return a null-terminated string array with the tokens in the input
* string, e.g. "one two\tthree" with a separator list of " \t" will return
* an array [ "one", "two", "three", NULL ].
*
* Use strv_free() to free the array.
*
* @param in Input string
* @param separators List of separator characters
*
* @return A null-terminated string array or NULL on errors
*/
char **
strv_from_string(const char *in, const char *separators)
{
const char *s, *word;
char **strv = NULL;
int nelems = 0, idx;
size_t l;
assert(in != NULL);
s = in;
while (next_word(&s, &l, separators) != NULL)
nelems++;
if (nelems == 0)
return NULL;
nelems++; /* NULL-terminated */
strv = xalloc(nelems * sizeof *strv);
idx = 0;
s = in;
while ((word = next_word(&s, &l, separators)) != NULL) {
char *copy = strndup(word, l);
if (!copy) {
strv_free(strv);
return NULL;
}
strv[idx++] = copy;
}
return strv;
}
/**
* Return a newly allocated string with all elements joined by the
* joiner, same as Python's string.join() basically.
* A strv of ["one", "two", "three", NULL] with a joiner of ", " results
* in "one, two, three".
*
* An empty strv ([NULL]) returns NULL, same for passing NULL as either
* argument.
*
* @param strv Input string arrray
* @param joiner Joiner between the elements in the final string
*
* @return A null-terminated string joining all elements
*/
char *
strv_join(char **strv, const char *joiner)
{
char **s;
char *str;
size_t slen = 0;
size_t count = 0;
if (!strv || !joiner)
return NULL;
if (strv[0] == NULL)
return NULL;
for (s = strv, count = 0; *s; s++, count++) {
slen += strlen(*s);
}
assert(slen < 1000);
assert(strlen(joiner) < 1000);
assert(count > 0);
assert(count < 100);
slen += (count - 1) * strlen(joiner);
str = xalloc(slen + 1); /* trailing \0 */
for (s = strv; *s; s++) {
strcat(str, *s);
--count;
if (count > 0)
strcat(str, joiner);
}
return str;
}
#if _enable_tests_
#include "util-munit.h"
MUNIT_TEST(test_strsplit)
{
struct strsplit_test {
const char *string;
const char *delim;
const char *results[10];
} tests[] = {
{ "one two three", " ", { "one", "two", "three", NULL } },
{ "one", " ", { "one", NULL } },
{ "one two ", " ", { "one", "two", NULL } },
{ "one two", " ", { "one", "two", NULL } },
{ " one two", " ", { "one", "two", NULL } },
{ "one", "\t \r", { "one", NULL } },
{ "one two three", " t", { "one", "wo", "hree", NULL } },
{ " one two three", "te", { " on", " ", "wo ", "hr", NULL } },
{ "one", "ne", { "o", NULL } },
{ "onene", "ne", { "o", NULL } },
{ NULL, NULL, { NULL }}
};
struct strsplit_test *t = tests;
while (t->string) {
char **strv;
int idx = 0;
strv = strv_from_string(t->string, t->delim);
while (t->results[idx]) {
munit_assert_string_equal(t->results[idx], strv[idx]);
idx++;
}
munit_assert_ptr_equal(strv[idx], NULL);
strv_free(strv);
t++;
}
/* Special cases */
munit_assert_ptr_equal(strv_from_string("", " "), NULL);
munit_assert_ptr_equal(strv_from_string(" ", " "), NULL);
munit_assert_ptr_equal(strv_from_string(" ", " "), NULL);
munit_assert_ptr_equal(strv_from_string("oneoneone", "one"), NULL);
return MUNIT_OK;
}
MUNIT_TEST(test_kvsplit_double)
{
struct kvsplit_dbl_test {
const char *string;
const char *psep;
const char *kvsep;
ssize_t nresults;
struct {
double a;
double b;
} results[32];
} tests[] = {
{ "1:2;3:4;5:6", ";", ":", 3, { {1, 2}, {3, 4}, {5, 6}}},
{ "1.0x2.3 -3.2x4.5 8.090909x-6.00", " ", "x", 3, { {1.0, 2.3}, {-3.2, 4.5}, {8.090909, -6}}},
{ "1:2", "x", ":", 1, {{1, 2}}},
{ "1:2", ":", "x", -1, {}},
{ "1:2", NULL, "x", -1, {}},
{ "1:2", "", "x", -1, {}},
{ "1:2", "x", NULL, -1, {}},
{ "1:2", "x", "", -1, {}},
{ "a:b", "x", ":", -1, {}},
{ "", " ", "x", -1, {}},
{ "1.2.3.4.5", ".", "", -1, {}},
{ NULL }
};
struct kvsplit_dbl_test *t = tests;
while (t->string) {
struct key_value_double *result = NULL;
ssize_t npairs;
npairs = kv_double_from_string(t->string,
t->psep,
t->kvsep,
&result);
munit_assert_int(npairs, ==, t->nresults);
for (ssize_t i = 0; i < npairs; i++) {
munit_assert_double(t->results[i].a, ==, result[i].key);
munit_assert_double(t->results[i].b, ==, result[i].value);
}
free(result);
t++;
}
return MUNIT_OK;
}
MUNIT_TEST(test_strjoin)
{
struct strjoin_test {
char *strv[10];
const char *joiner;
const char *result;
} tests[] = {
{ { "one", "two", "three", NULL }, " ", "one two three" },
{ { "one", NULL }, "x", "one" },
{ { "one", "two", NULL }, "x", "onextwo" },
{ { "one", "two", NULL }, ",", "one,two" },
{ { "one", "two", NULL }, ", ", "one, two" },
{ { "one", "two", NULL }, "one", "oneonetwo" },
{ { "one", "two", NULL }, NULL, NULL },
{ { "", "", "", NULL }, " ", " " },
{ { "a", "b", "c", NULL }, "", "abc" },
{ { "", "b", "c", NULL }, "x", "xbxc" },
{ { "", "", "", NULL }, "", "" },
{ { NULL }, NULL, NULL }
};
struct strjoin_test *t = tests;
struct strjoin_test nulltest = { {NULL}, "x", NULL };
while (t->strv[0]) {
char *str;
str = strv_join(t->strv, t->joiner);
if (t->result == NULL)
munit_assert(str == NULL);
else
munit_assert_string_equal(str, t->result);
free(str);
t++;
}
munit_assert(strv_join(nulltest.strv, "x") == NULL);
return MUNIT_OK;
}
MUNIT_TEST(test_strstrip)
{
struct strstrip_test {
const char *string;
const char *expected;
const char *what;
} tests[] = {
{ "foo", "foo", "1234" },
{ "\"bar\"", "bar", "\"" },
{ "'bar'", "bar", "'" },
{ "\"bar\"", "\"bar\"", "'" },
{ "'bar'", "'bar'", "\"" },
{ "\"bar\"", "bar", "\"" },
{ "\"\"", "", "\"" },
{ "\"foo\"bar\"", "foo\"bar", "\"" },
{ "\"'foo\"bar\"", "foo\"bar", "\"'" },
{ "abcfooabcbarbca", "fooabcbar", "abc" },
{ "xxxxfoo", "foo", "x" },
{ "fooyyyy", "foo", "y" },
{ "xxxxfooyyyy", "foo", "xy" },
{ "x xfooy y", " xfooy ", "xy" },
{ " foo\n", "foo", " \n" },
{ "", "", "abc" },
{ "", "", "" },
{ NULL , NULL, NULL }
};
struct strstrip_test *t = tests;
while (t->string) {
char *str;
str = strstrip(t->string, t->what);
munit_assert_string_equal(str, t->expected);
free(str);
t++;
}
return MUNIT_OK;
}
MUNIT_TEST(test_strstartswith)
{
struct strstartswith_test {
const char *string;
const char *suffix;
bool expected;
} tests[] = {
{ "foobar", "foo", true },
{ "foobar", "bar", false },
{ "foobar", "foobar", true },
{ "foo", "foobar", false },
{ "foo", "", false },
{ "", "", false },
{ "foo", "", false },
{ NULL, NULL, false },
};
for (struct strstartswith_test *t = tests; t->string; t++) {
munit_assert_int(strstartswith(t->string, t->suffix), ==, t->expected);
}
return MUNIT_OK;
}
MUNIT_TEST(test_strendswith)
{
struct strendswith_test {
const char *string;
const char *suffix;
bool expected;
} tests[] = {
{ "foobar", "bar", true },
{ "foobar", "foo", false },
{ "foobar", "foobar", true },
{ "foo", "foobar", false },
{ "foobar", "", false },
{ "", "", false },
{ "", "foo", false },
{ NULL, NULL, false },
};
for (struct strendswith_test *t = tests; t->string; t++) {
munit_assert_int(strendswith(t->string, t->suffix), ==, t->expected);
}
return MUNIT_OK;
}
#endif