Skip to content

Commit

Permalink
Refactor conversion of string to/from string array list
Browse files Browse the repository at this point in the history
  • Loading branch information
pnoltes committed Jan 28, 2024
1 parent 4841034 commit 51db964
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 37 deletions.
28 changes: 27 additions & 1 deletion libs/utils/gtest/src/ConvertUtilsErrorInjectionTestSuite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class ConvertUtilsWithErrorInjectionTestSuite : public ::testing::Test {
celix_ei_expect_celix_arrayList_addLong(nullptr, 0, CELIX_SUCCESS);
celix_ei_expect_open_memstream(nullptr, 0, nullptr);
celix_ei_expect_fputs(nullptr, 0, 0);
celix_ei_expect_fputc(nullptr, 0, 0);

celix_err_printErrors(stderr, nullptr, nullptr);
}
Expand Down Expand Up @@ -116,4 +117,29 @@ TEST_F(ConvertUtilsWithErrorInjectionTestSuite, LongArrayToStringTest) {
result = celix_utils_longArrayListToString(list);
//Then the result is null
EXPECT_EQ(nullptr, result);
}
}

TEST_F(ConvertUtilsWithErrorInjectionTestSuite, StringToStringArrayTest) {
// Given an error injection for open_memstream (on the second call)
celix_ei_expect_open_memstream((void*)celix_utils_convertStringToStringArrayList, 1, nullptr, 2);
// When calling celix_utils_convertStringToStringArrayList
celix_array_list_t* result;
celix_status_t status = celix_utils_convertStringToStringArrayList("a,b,c", nullptr, &result);
// Then the result is null and the status is ENOMEM
EXPECT_EQ(status, CELIX_ENOMEM);
EXPECT_EQ(nullptr, result);

// Given an error injection for fputc
celix_ei_expect_fputc((void*)celix_utils_convertStringToStringArrayList, 1, EOF);
// When calling celix_utils_convertStringToStringArrayList
status = celix_utils_convertStringToStringArrayList("a,b,c", nullptr, &result);
// Then the result is null and the status is ENOMEM
EXPECT_EQ(status, CELIX_ENOMEM);

// Given an error injection for fputc (on writing an escaped char)
celix_ei_expect_fputc((void*)celix_utils_convertStringToStringArrayList, 1, EOF);
// When calling celix_utils_convertStringToStringArrayList
status = celix_utils_convertStringToStringArrayList(R"(\\)", nullptr, &result);
// Then the result is null and the status is ENOMEM
EXPECT_EQ(status, CELIX_ENOMEM);
}
72 changes: 57 additions & 15 deletions libs/utils/gtest/src/ConvertUtilsTestSuite.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class ConvertUtilsTestSuite : public ::testing::Test {
public:
~ConvertUtilsTestSuite() noexcept override { celix_err_printErrors(stderr, nullptr, nullptr); }

void checkVersion(const celix_version_t* version, int major, int minor, int micro, const char* qualifier) {
static void checkVersion(const celix_version_t* version, int major, int minor, int micro, const char* qualifier) {
EXPECT_TRUE(version != nullptr);
if (version) {
EXPECT_EQ(major, celix_version_getMajor(version));
Expand Down Expand Up @@ -114,8 +114,9 @@ TEST_F(ConvertUtilsTestSuite, ConvertToDoubleTest) {
EXPECT_EQ(1.0, result);
EXPECT_FALSE(converted);

//test for a string with a invalid number
//test for a string with an invalid number
result = celix_utils_convertStringToDouble("10.5A", 0, &converted);
EXPECT_EQ(0, result);
EXPECT_FALSE(converted);

//test for a string with a number and a negative sign
Expand All @@ -139,6 +140,7 @@ TEST_F(ConvertUtilsTestSuite, ConvertToDoubleTest) {

//test for a convert with an invalid double value with trailing info
result = celix_utils_convertStringToDouble("11.1.2", 0, &converted);
EXPECT_EQ(0, result);
EXPECT_FALSE(converted);

//test for a convert with a double value with trailing whitespaces
Expand Down Expand Up @@ -194,16 +196,17 @@ TEST_F(ConvertUtilsTestSuite, ConvertToBoolTest) {
EXPECT_EQ(true, result);

//test for a convert with a bool value with trailing chars
result = celix_utils_convertStringToBool("true and ok", 0, &converted);
result = celix_utils_convertStringToBool("true and ok", false, &converted);
EXPECT_FALSE(result);
EXPECT_FALSE(converted);

//test for a convert with a bool value with trailing whitespaces
result = celix_utils_convertStringToBool("true \t\n", 0, &converted);
result = celix_utils_convertStringToBool("true \t\n", false, &converted);
EXPECT_TRUE(result);
EXPECT_TRUE(converted);

//test for a convert with a bool value with starting and trailing whitespaces
result = celix_utils_convertStringToBool("\t false \t\n", 0, &converted);
result = celix_utils_convertStringToBool("\t false \t\n", false, &converted);
EXPECT_FALSE(result);
EXPECT_TRUE(converted);

Expand Down Expand Up @@ -345,7 +348,7 @@ TEST_F(ConvertUtilsTestSuite, LongArrayToStringTest) {
celix_arrayList_addLong(list, 3L);

char* result = celix_utils_longArrayListToString(list);
EXPECT_STREQ("1, 2, 3", result);
EXPECT_STREQ("1,2,3", result);
free(result);

celix_autoptr(celix_array_list_t) emptyList = celix_arrayList_create();
Expand Down Expand Up @@ -415,7 +418,7 @@ TEST_F(ConvertUtilsTestSuite, BoolArrayToStringTest) {
celix_arrayList_addBool(list, true);

char* result = celix_utils_boolArrayListToString(list);
EXPECT_STREQ("true, false, true", result);
EXPECT_STREQ("true,false,true", result);
free(result);

// NOTE celix_utils_boolArrayListToString uses the same generic function as is used in
Expand All @@ -434,24 +437,63 @@ TEST_F(ConvertUtilsTestSuite, ConvertToStringArrayList) {
EXPECT_STREQ("c", (char*)celix_arrayList_get(result, 2));
celix_arrayList_destroy(result);

convertState = celix_utils_convertStringToStringArrayList(R"(a,b\\\,,c)", nullptr, &result);
EXPECT_EQ(CELIX_SUCCESS, convertState);
EXPECT_TRUE(result != nullptr);
EXPECT_EQ(3, celix_arrayList_size(result));
EXPECT_STREQ("a", celix_arrayList_getString(result, 0));
EXPECT_STREQ("b\\,", celix_arrayList_getString(result, 1));
EXPECT_STREQ("c", celix_arrayList_getString(result, 2));
celix_arrayList_destroy(result);

convertState = celix_utils_convertStringToStringArrayList("a,,b,", nullptr, &result); //4 entries, second and last are empty strings
EXPECT_EQ(CELIX_SUCCESS, convertState);
EXPECT_TRUE(result != nullptr);
EXPECT_EQ(4, celix_arrayList_size(result));
EXPECT_STREQ("a", celix_arrayList_getString(result, 0));
EXPECT_STREQ("", celix_arrayList_getString(result, 1));
EXPECT_STREQ("b", celix_arrayList_getString(result, 2));
EXPECT_STREQ("", celix_arrayList_getString(result, 3));
celix_arrayList_destroy(result);

//invalid escape sequence
convertState = celix_utils_convertStringToStringArrayList(R"(a,b\c,d)", nullptr, &result);
EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, convertState);
EXPECT_TRUE(result == nullptr);
convertState = celix_utils_convertStringToStringArrayList(R"(a,b,c\)", nullptr, &result);
EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, convertState);
EXPECT_TRUE(result == nullptr);

// NOTE celix_utils_convertStringToStringArrayList uses the same generic function as is used in
// celix_utils_convertStringToLongArrayList and because celix_utils_convertStringToLongArrayList is already
// tested, we only test a few cases here.
}

TEST_F(ConvertUtilsTestSuite, StringArrayToStringTest) {
celix_autoptr(celix_array_list_t) list = celix_arrayList_create();
celix_arrayList_add(list, (void*)"a");
celix_arrayList_add(list, (void*)"b");
celix_arrayList_add(list, (void*)"c");
celix_arrayList_addString(list, "a");
celix_arrayList_addString(list, "b");
celix_arrayList_addString(list, "c");

char* result = celix_utils_stringArrayListToString(list);
EXPECT_STREQ("a, b, c", result);
EXPECT_STREQ("a,b,c", result);
free(result);

// NOTE celix_utils_stringArrayListToString uses the same generic function as is used in
// celix_utils_longArrayListToString and because celix_utils_longArrayListToString is already
// tested, we only test a few cases here.
celix_arrayList_addString(list, "d\\,");
celix_arrayList_addString(list, "e");
result = celix_utils_stringArrayListToString(list);
EXPECT_STREQ(R"(a,b,c,d\\\,,e)", result);

//Check if the result can be converted back to an equal list
celix_array_list_t* listResult;
celix_status_t convertState = celix_utils_convertStringToStringArrayList(result, nullptr, &listResult);
EXPECT_EQ(CELIX_SUCCESS, convertState);
EXPECT_TRUE(listResult != nullptr);

EXPECT_EQ(celix_arrayList_size(list), celix_arrayList_size(listResult));
for (int i = 0; i < celix_arrayList_size(list); ++i) {
EXPECT_STREQ((char*)celix_arrayList_get(list, i), (char*)celix_arrayList_get(listResult, i));
}
}

TEST_F(ConvertUtilsTestSuite, ConvertToVersionArrayList) {
Expand Down Expand Up @@ -480,7 +522,7 @@ TEST_F(ConvertUtilsTestSuite, VersionArrayToStringTest) {
celix_arrayList_add(list, v3);

char* result = celix_utils_versionArrayListToString(list);
EXPECT_STREQ("1.2.3, 2.3.4, 3.4.5.qualifier", result);
EXPECT_STREQ("1.2.3,2.3.4,3.4.5.qualifier", result);
free(result);

// NOTE celix_utils_versionArrayListToString uses the same generic function as is used in
Expand Down
2 changes: 1 addition & 1 deletion libs/utils/include/celix_array_list.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ extern "C" {
#endif

typedef union celix_array_list_entry {
void *voidPtrVal;
void* voidPtrVal;
const char* strVal;
int intVal;
long int longVal;
Expand Down
3 changes: 3 additions & 0 deletions libs/utils/include/celix_convert_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ char* celix_utils_boolArrayListToString(const celix_array_list_t* list);
* The expected format of the string is a "," separated list of strings. Whitespace is preserved.
* String entries are copied and the returned list will be configured to call free when entries are removed.
*
* The escaped character is "\" and can be used to escape "," and "\" characters.
* E.g. "a,b\,\\,c" will be converted to "a", "b,\" and "c".
*
* @param[in] val The string to convert.
* @param[in] defaultValue The default value if the string is not a valid "," separated list of strings.
* Note that the defaultValue is copied if the string is not a valid list of string entries
Expand Down
81 changes: 61 additions & 20 deletions libs/utils/src/celix_convert_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@

#include "celix_array_list.h"
#include "celix_err.h"
#include "celix_stdio_cleanup.h"
#include "celix_utils.h"

#define ESCAPE_CHAR '\\'
#define SEPARATOR_CHAR ','

static bool celix_utils_isEndptrEndOfStringOrOnlyContainsWhitespaces(const char* endptr) {
bool result = false;
if (endptr != NULL) {
Expand Down Expand Up @@ -116,8 +118,6 @@ celix_utils_convertStringToVersion(const char* val, const celix_version_t* defau
if (!val && defaultValue) {
*version = celix_version_copy(defaultValue);
return *version ? CELIX_ILLEGAL_ARGUMENT : CELIX_ENOMEM;
} else if (!val) {
return CELIX_ILLEGAL_ARGUMENT;
}

celix_status_t status = celix_version_parse(val, version);
Expand Down Expand Up @@ -158,23 +158,50 @@ static celix_status_t celix_utils_convertStringToArrayList(const char* val,
return CELIX_ENOMEM;
}

char buf[256];
char* valCopy = celix_utils_writeOrCreateString(buf, sizeof(buf), "%s", val);
if (!valCopy) {
return CELIX_ENOMEM;
}

char* buf = NULL;
size_t bufSize = 0;
FILE* entryStream = NULL;
celix_status_t status = CELIX_SUCCESS;
char* savePtr = NULL;
char* token = strtok_r(valCopy, ",", &savePtr);
while (token != NULL) {
status = addEntry(result, token);
if (status != CELIX_SUCCESS) {
break;
size_t max = strlen(val);
for (size_t i = 0; i <= max; ++i) {
if (!entryStream) {
entryStream = open_memstream(&buf, &bufSize);
if (!entryStream) {
return CELIX_ENOMEM;
}
}
if (val[i] == ESCAPE_CHAR) {
// escape character, next char must be escapeChar or separatorChar
if (i + 1 < max && (val[i + 1] == ESCAPE_CHAR || val[i + 1] == SEPARATOR_CHAR)) {
// write escaped char
i += 1;
int rc = fputc(val[i], entryStream);
if (rc == EOF) {
return CELIX_ENOMEM;
}
continue;
} else {
// invalid escape (ending with escapeChar or followed by an invalid char)
status = CELIX_ILLEGAL_ARGUMENT;
break;
}
} else if (val[i] == SEPARATOR_CHAR || val[i] == '\0') {
//end of entry
fclose(entryStream);
entryStream = NULL;
status = addEntry(result, buf);
if (status == CELIX_ENOMEM) {
return status;
}
} else {
//normal char
int rc = fputc(val[i], entryStream);
if (rc == EOF) {
return CELIX_ENOMEM;
}
}
token = strtok_r(NULL, ",", &savePtr);
}
celix_utils_freeStringIfNotEqual(buf, valCopy);


if (status == CELIX_SUCCESS) {
*list = celix_steal_ptr(result);
Expand Down Expand Up @@ -220,7 +247,7 @@ celix_status_t celix_utils_convertStringToDoubleArrayList(const char* val,

celix_status_t celix_utils_addBoolEntry(celix_array_list_t* list, const char* entry) {
bool converted;
bool b = celix_utils_convertStringToBool(entry, 0.0, &converted);
bool b = celix_utils_convertStringToBool(entry, true, &converted);
if (!converted) {
return CELIX_ILLEGAL_ARGUMENT;
}
Expand Down Expand Up @@ -289,7 +316,7 @@ static char* celix_utils_arrayListToString(const celix_array_list_t* list,
celix_array_list_entry_t entry = celix_arrayList_getEntry(list, i);
int rc = printCb(stream, &entry);
if (rc >= 0 && i < size - 1) {
rc = fputs(", ", stream);
rc = fputs(",", stream);
}
if (rc < 0) {
celix_err_push("Cannot print to stream");
Expand Down Expand Up @@ -327,7 +354,21 @@ char* celix_utils_boolArrayListToString(const celix_array_list_t* list) {
}

static int celix_utils_printStrEntry(FILE* stream, const celix_array_list_entry_t* entry) {
return fprintf(stream, "%s", (const char*)entry->voidPtrVal);
const char* str = entry->strVal;
int rc = 0;
for (int i = 0; str[i] != '\0'; ++i) {
if (str[i] == ESCAPE_CHAR || str[i] == SEPARATOR_CHAR) {
//both escape and separator char need to be escaped
rc = fputc(ESCAPE_CHAR, stream);
}
if (rc != EOF) {
rc = fputc(str[i], stream);
}
if (rc == EOF) {
break;
}
}
return rc;
}

char* celix_utils_stringArrayListToString(const celix_array_list_t* list) {
Expand Down

0 comments on commit 51db964

Please sign in to comment.