Skip to content

Commit 1c222f7

Browse files
authored
Improve performance of sdssplitargs (#1230)
The current implementation of `sdssplitargs` does repeated `sdscatlen` to build the parsed arguments, which isn't very efficient because it does a lot of extra reallocations and moves through the sds code a lot. It also typically results in memory overhead, because `sdscatlen` over-allocates, which is usually not needed since args are usually not modified after being created. The new implementation of sdssplitargs does two passes, the first to parse the argument to figure out the final length and the second to actually copy the string. It's generally about 2x faster for larger strings (~100 bytes), and about 20% faster for small strings (~10 bytes). This is generally faster since as long as everything is in the CPU cache, it's going to be fast. There are a couple of sanity tests, none existed before, as well as some fuzzying which was used to find some bugs and also to do the benchmarking. The original benchmarking code can be seen 6576aeb. ``` test_sdssplitargs_benchmark - unit/test_sds.c:530] Using random seed: 1729883235 [test_sdssplitargs_benchmark - unit/test_sds.c:577] Improvement: 56.44%, new:13039us, old:29930us [test_sdssplitargs_benchmark - unit/test_sds.c:577] Improvement: 56.58%, new:12057us, old:27771us [test_sdssplitargs_benchmark - unit/test_sds.c:577] Improvement: 59.18%, new:9048us, old:22165us [test_sdssplitargs_benchmark - unit/test_sds.c:577] Improvement: 54.61%, new:12381us, old:27278us [test_sdssplitargs_benchmark - unit/test_sds.c:577] Improvement: 51.17%, new:16012us, old:32793us [test_sdssplitargs_benchmark - unit/test_sds.c:577] Improvement: 49.18%, new:16041us, old:31563us [test_sdssplitargs_benchmark - unit/test_sds.c:577] Improvement: 58.40%, new:12450us, old:29930us [test_sdssplitargs_benchmark - unit/test_sds.c:577] Improvement: 56.49%, new:13066us, old:30031us [test_sdssplitargs_benchmark - unit/test_sds.c:577] Improvement: 58.75%, new:12744us, old:30894us [test_sdssplitargs_benchmark - unit/test_sds.c:577] Improvement: 52.44%, new:16885us, old:35504us [test_sdssplitargs_benchmark - unit/test_sds.c:577] Improvement: 62.57%, new:8107us, old:21659us [test_sdssplitargs_benchmark - unit/test_sds.c:577] Improvement: 62.12%, new:8320us, old:21966us [test_sdssplitargs_benchmark - unit/test_sds.c:577] Improvement: 45.23%, new:13960us, old:25487us [test_sdssplitargs_benchmark - unit/test_sds.c:577] Improvement: 57.95%, new:9188us, old:21849us ``` --------- Signed-off-by: Madelyn Olson <[email protected]>
1 parent 91cbf77 commit 1c222f7

File tree

3 files changed

+143
-82
lines changed

3 files changed

+143
-82
lines changed

src/sds.c

Lines changed: 100 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,86 @@ int hex_digit_to_int(char c) {
10321032
}
10331033
}
10341034

1035+
/* Helper function for sdssplitargs that parses a single argument. It
1036+
* populates the number characters needed to store the parsed argument
1037+
* in len, if provided, or will copy the parsed string into dst, if provided.
1038+
* If the string is able to be parsed, this function returns the number of
1039+
* characters that were parsed. If the argument can't be parsed, it
1040+
* returns 0. */
1041+
static int sdsparsearg(const char *arg, unsigned int *len, char *dst) {
1042+
const char *p = arg;
1043+
int inq = 0; /* set to 1 if we are in "quotes" */
1044+
int insq = 0; /* set to 1 if we are in 'single quotes' */
1045+
int done = 0;
1046+
1047+
while (!done) {
1048+
int new_char = -1;
1049+
if (inq) {
1050+
if (*p == '\\' && *(p + 1) == 'x' && is_hex_digit(*(p + 2)) && is_hex_digit(*(p + 3))) {
1051+
new_char = (hex_digit_to_int(*(p + 2)) * 16) + hex_digit_to_int(*(p + 3));
1052+
p += 3;
1053+
} else if (*p == '\\' && *(p + 1)) {
1054+
p++;
1055+
switch (*p) {
1056+
case 'n': new_char = '\n'; break;
1057+
case 'r': new_char = '\r'; break;
1058+
case 't': new_char = '\t'; break;
1059+
case 'b': new_char = '\b'; break;
1060+
case 'a': new_char = '\a'; break;
1061+
default: new_char = *p; break;
1062+
}
1063+
} else if (*p == '"') {
1064+
/* closing quote must be followed by a space or
1065+
* nothing at all. */
1066+
if (*(p + 1) && !isspace(*(p + 1))) return 0;
1067+
done = 1;
1068+
} else if (!*p) {
1069+
/* unterminated quotes */
1070+
return 0;
1071+
} else {
1072+
new_char = *p;
1073+
}
1074+
} else if (insq) {
1075+
if (*p == '\\' && *(p + 1) == '\'') {
1076+
p++;
1077+
new_char = *p;
1078+
} else if (*p == '\'') {
1079+
/* closing quote must be followed by a space or
1080+
* nothing at all. */
1081+
if (*(p + 1) && !isspace(*(p + 1))) return 0;
1082+
done = 1;
1083+
} else if (!*p) {
1084+
/* unterminated quotes */
1085+
return 0;
1086+
} else {
1087+
new_char = *p;
1088+
}
1089+
} else {
1090+
switch (*p) {
1091+
case ' ':
1092+
case '\n':
1093+
case '\r':
1094+
case '\t':
1095+
case '\0': done = 1; break;
1096+
case '"': inq = 1; break;
1097+
case '\'': insq = 1; break;
1098+
default: new_char = *p; break;
1099+
}
1100+
}
1101+
if (new_char != -1) {
1102+
if (len) (*len)++;
1103+
if (dst) {
1104+
*dst = (char)new_char;
1105+
dst++;
1106+
}
1107+
}
1108+
if (*p) {
1109+
p++;
1110+
}
1111+
}
1112+
return p - arg;
1113+
}
1114+
10351115
/* Split a line into arguments, where every argument can be in the
10361116
* following programming-language REPL-alike form:
10371117
*
@@ -1049,103 +1129,42 @@ int hex_digit_to_int(char c) {
10491129
* The function returns the allocated tokens on success, even when the
10501130
* input string is empty, or NULL if the input contains unbalanced
10511131
* quotes or closed quotes followed by non space characters
1052-
* as in: "foo"bar or "foo'
1132+
* as in: "foo"bar or "foo'.
1133+
*
1134+
* The sds strings returned by this function are not initialized with
1135+
* extra space.
10531136
*/
10541137
sds *sdssplitargs(const char *line, int *argc) {
10551138
const char *p = line;
1056-
char *current = NULL;
10571139
char **vector = NULL;
10581140

10591141
*argc = 0;
1060-
while (1) {
1142+
while (*p) {
10611143
/* skip blanks */
10621144
while (*p && isspace(*p)) p++;
1063-
if (*p) {
1064-
/* get a token */
1065-
int inq = 0; /* set to 1 if we are in "quotes" */
1066-
int insq = 0; /* set to 1 if we are in 'single quotes' */
1067-
int done = 0;
1068-
1069-
if (current == NULL) current = sdsempty();
1070-
while (!done) {
1071-
if (inq) {
1072-
if (*p == '\\' && *(p + 1) == 'x' && is_hex_digit(*(p + 2)) && is_hex_digit(*(p + 3))) {
1073-
unsigned char byte;
1074-
1075-
byte = (hex_digit_to_int(*(p + 2)) * 16) + hex_digit_to_int(*(p + 3));
1076-
current = sdscatlen(current, (char *)&byte, 1);
1077-
p += 3;
1078-
} else if (*p == '\\' && *(p + 1)) {
1079-
char c;
1080-
1081-
p++;
1082-
switch (*p) {
1083-
case 'n': c = '\n'; break;
1084-
case 'r': c = '\r'; break;
1085-
case 't': c = '\t'; break;
1086-
case 'b': c = '\b'; break;
1087-
case 'a': c = '\a'; break;
1088-
default: c = *p; break;
1089-
}
1090-
current = sdscatlen(current, &c, 1);
1091-
} else if (*p == '"') {
1092-
/* closing quote must be followed by a space or
1093-
* nothing at all. */
1094-
if (*(p + 1) && !isspace(*(p + 1))) goto err;
1095-
done = 1;
1096-
} else if (!*p) {
1097-
/* unterminated quotes */
1098-
goto err;
1099-
} else {
1100-
current = sdscatlen(current, p, 1);
1101-
}
1102-
} else if (insq) {
1103-
if (*p == '\\' && *(p + 1) == '\'') {
1104-
p++;
1105-
current = sdscatlen(current, "'", 1);
1106-
} else if (*p == '\'') {
1107-
/* closing quote must be followed by a space or
1108-
* nothing at all. */
1109-
if (*(p + 1) && !isspace(*(p + 1))) goto err;
1110-
done = 1;
1111-
} else if (!*p) {
1112-
/* unterminated quotes */
1113-
goto err;
1114-
} else {
1115-
current = sdscatlen(current, p, 1);
1116-
}
1117-
} else {
1118-
switch (*p) {
1119-
case ' ':
1120-
case '\n':
1121-
case '\r':
1122-
case '\t':
1123-
case '\0': done = 1; break;
1124-
case '"': inq = 1; break;
1125-
case '\'': insq = 1; break;
1126-
default: current = sdscatlen(current, p, 1); break;
1127-
}
1128-
}
1129-
if (*p) p++;
1130-
}
1145+
if (!(*p)) break;
1146+
unsigned int len = 0;
1147+
if (sdsparsearg(p, &len, NULL)) {
1148+
sds current = sdsnewlen(SDS_NOINIT, len);
1149+
int parsedlen = sdsparsearg(p, NULL, current);
1150+
assert(parsedlen > 0);
1151+
p += parsedlen;
1152+
11311153
/* add the token to the vector */
11321154
vector = s_realloc(vector, ((*argc) + 1) * sizeof(char *));
11331155
vector[*argc] = current;
11341156
(*argc)++;
11351157
current = NULL;
11361158
} else {
1137-
/* Even on empty input string return something not NULL. */
1138-
if (vector == NULL) vector = s_malloc(sizeof(void *));
1139-
return vector;
1159+
while ((*argc)--) sdsfree(vector[*argc]);
1160+
s_free(vector);
1161+
*argc = 0;
1162+
return NULL;
11401163
}
11411164
}
1142-
1143-
err:
1144-
while ((*argc)--) sdsfree(vector[*argc]);
1145-
s_free(vector);
1146-
if (current) sdsfree(current);
1147-
*argc = 0;
1148-
return NULL;
1165+
/* Even on empty input string return something not NULL. */
1166+
if (vector == NULL) vector = s_malloc(sizeof(void *));
1167+
return vector;
11491168
}
11501169

11511170
/* Modify the string substituting all the occurrences of the set of

src/unit/test_files.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ int test_raxFuzz(int argc, char **argv, int flags);
9999
int test_sds(int argc, char **argv, int flags);
100100
int test_typesAndAllocSize(int argc, char **argv, int flags);
101101
int test_sdsHeaderSizes(int argc, char **argv, int flags);
102+
int test_sdssplitargs(int argc, char **argv, int flags);
102103
int test_sha1(int argc, char **argv, int flags);
103104
int test_string2ll(int argc, char **argv, int flags);
104105
int test_string2l(int argc, char **argv, int flags);
@@ -157,7 +158,7 @@ unitTest __test_intset_c[] = {{"test_intsetValueEncodings", test_intsetValueEnco
157158
unitTest __test_kvstore_c[] = {{"test_kvstoreAdd16Keys", test_kvstoreAdd16Keys}, {"test_kvstoreIteratorRemoveAllKeysNoDeleteEmptyDict", test_kvstoreIteratorRemoveAllKeysNoDeleteEmptyDict}, {"test_kvstoreIteratorRemoveAllKeysDeleteEmptyDict", test_kvstoreIteratorRemoveAllKeysDeleteEmptyDict}, {"test_kvstoreDictIteratorRemoveAllKeysNoDeleteEmptyDict", test_kvstoreDictIteratorRemoveAllKeysNoDeleteEmptyDict}, {"test_kvstoreDictIteratorRemoveAllKeysDeleteEmptyDict", test_kvstoreDictIteratorRemoveAllKeysDeleteEmptyDict}, {NULL, NULL}};
158159
unitTest __test_listpack_c[] = {{"test_listpackCreateIntList", test_listpackCreateIntList}, {"test_listpackCreateList", test_listpackCreateList}, {"test_listpackLpPrepend", test_listpackLpPrepend}, {"test_listpackLpPrependInteger", test_listpackLpPrependInteger}, {"test_listpackGetELementAtIndex", test_listpackGetELementAtIndex}, {"test_listpackPop", test_listpackPop}, {"test_listpackGetELementAtIndex2", test_listpackGetELementAtIndex2}, {"test_listpackIterate0toEnd", test_listpackIterate0toEnd}, {"test_listpackIterate1toEnd", test_listpackIterate1toEnd}, {"test_listpackIterate2toEnd", test_listpackIterate2toEnd}, {"test_listpackIterateBackToFront", test_listpackIterateBackToFront}, {"test_listpackIterateBackToFrontWithDelete", test_listpackIterateBackToFrontWithDelete}, {"test_listpackDeleteWhenNumIsMinusOne", test_listpackDeleteWhenNumIsMinusOne}, {"test_listpackDeleteWithNegativeIndex", test_listpackDeleteWithNegativeIndex}, {"test_listpackDeleteInclusiveRange0_0", test_listpackDeleteInclusiveRange0_0}, {"test_listpackDeleteInclusiveRange0_1", test_listpackDeleteInclusiveRange0_1}, {"test_listpackDeleteInclusiveRange1_2", test_listpackDeleteInclusiveRange1_2}, {"test_listpackDeleteWitStartIndexOutOfRange", test_listpackDeleteWitStartIndexOutOfRange}, {"test_listpackDeleteWitNumOverflow", test_listpackDeleteWitNumOverflow}, {"test_listpackBatchDelete", test_listpackBatchDelete}, {"test_listpackDeleteFooWhileIterating", test_listpackDeleteFooWhileIterating}, {"test_listpackReplaceWithSameSize", test_listpackReplaceWithSameSize}, {"test_listpackReplaceWithDifferentSize", test_listpackReplaceWithDifferentSize}, {"test_listpackRegressionGt255Bytes", test_listpackRegressionGt255Bytes}, {"test_listpackCreateLongListAndCheckIndices", test_listpackCreateLongListAndCheckIndices}, {"test_listpackCompareStrsWithLpEntries", test_listpackCompareStrsWithLpEntries}, {"test_listpackLpMergeEmptyLps", test_listpackLpMergeEmptyLps}, {"test_listpackLpMergeLp1Larger", test_listpackLpMergeLp1Larger}, {"test_listpackLpMergeLp2Larger", test_listpackLpMergeLp2Larger}, {"test_listpackLpNextRandom", test_listpackLpNextRandom}, {"test_listpackLpNextRandomCC", test_listpackLpNextRandomCC}, {"test_listpackRandomPairWithOneElement", test_listpackRandomPairWithOneElement}, {"test_listpackRandomPairWithManyElements", test_listpackRandomPairWithManyElements}, {"test_listpackRandomPairsWithOneElement", test_listpackRandomPairsWithOneElement}, {"test_listpackRandomPairsWithManyElements", test_listpackRandomPairsWithManyElements}, {"test_listpackRandomPairsUniqueWithOneElement", test_listpackRandomPairsUniqueWithOneElement}, {"test_listpackRandomPairsUniqueWithManyElements", test_listpackRandomPairsUniqueWithManyElements}, {"test_listpackPushVariousEncodings", test_listpackPushVariousEncodings}, {"test_listpackLpFind", test_listpackLpFind}, {"test_listpackLpValidateIntegrity", test_listpackLpValidateIntegrity}, {"test_listpackNumberOfElementsExceedsLP_HDR_NUMELE_UNKNOWN", test_listpackNumberOfElementsExceedsLP_HDR_NUMELE_UNKNOWN}, {"test_listpackStressWithRandom", test_listpackStressWithRandom}, {"test_listpackSTressWithVariableSize", test_listpackSTressWithVariableSize}, {"test_listpackBenchmarkInit", test_listpackBenchmarkInit}, {"test_listpackBenchmarkLpAppend", test_listpackBenchmarkLpAppend}, {"test_listpackBenchmarkLpFindString", test_listpackBenchmarkLpFindString}, {"test_listpackBenchmarkLpFindNumber", test_listpackBenchmarkLpFindNumber}, {"test_listpackBenchmarkLpSeek", test_listpackBenchmarkLpSeek}, {"test_listpackBenchmarkLpValidateIntegrity", test_listpackBenchmarkLpValidateIntegrity}, {"test_listpackBenchmarkLpCompareWithString", test_listpackBenchmarkLpCompareWithString}, {"test_listpackBenchmarkLpCompareWithNumber", test_listpackBenchmarkLpCompareWithNumber}, {"test_listpackBenchmarkFree", test_listpackBenchmarkFree}, {NULL, NULL}};
159160
unitTest __test_rax_c[] = {{"test_raxRandomWalk", test_raxRandomWalk}, {"test_raxIteratorUnitTests", test_raxIteratorUnitTests}, {"test_raxTryInsertUnitTests", test_raxTryInsertUnitTests}, {"test_raxRegressionTest1", test_raxRegressionTest1}, {"test_raxRegressionTest2", test_raxRegressionTest2}, {"test_raxRegressionTest3", test_raxRegressionTest3}, {"test_raxRegressionTest4", test_raxRegressionTest4}, {"test_raxRegressionTest5", test_raxRegressionTest5}, {"test_raxRegressionTest6", test_raxRegressionTest6}, {"test_raxBenchmark", test_raxBenchmark}, {"test_raxHugeKey", test_raxHugeKey}, {"test_raxFuzz", test_raxFuzz}, {NULL, NULL}};
160-
unitTest __test_sds_c[] = {{"test_sds", test_sds}, {"test_typesAndAllocSize", test_typesAndAllocSize}, {"test_sdsHeaderSizes", test_sdsHeaderSizes}, {NULL, NULL}};
161+
unitTest __test_sds_c[] = {{"test_sds", test_sds}, {"test_typesAndAllocSize", test_typesAndAllocSize}, {"test_sdsHeaderSizes", test_sdsHeaderSizes}, {"test_sdssplitargs", test_sdssplitargs}, {NULL, NULL}};
161162
unitTest __test_sha1_c[] = {{"test_sha1", test_sha1}, {NULL, NULL}};
162163
unitTest __test_util_c[] = {{"test_string2ll", test_string2ll}, {"test_string2l", test_string2l}, {"test_ll2string", test_ll2string}, {"test_ld2string", test_ld2string}, {"test_fixedpoint_d2string", test_fixedpoint_d2string}, {"test_version2num", test_version2num}, {"test_reclaimFilePageCache", test_reclaimFilePageCache}, {NULL, NULL}};
163164
unitTest __test_ziplist_c[] = {{"test_ziplistCreateIntList", test_ziplistCreateIntList}, {"test_ziplistPop", test_ziplistPop}, {"test_ziplistGetElementAtIndex3", test_ziplistGetElementAtIndex3}, {"test_ziplistGetElementOutOfRange", test_ziplistGetElementOutOfRange}, {"test_ziplistGetLastElement", test_ziplistGetLastElement}, {"test_ziplistGetFirstElement", test_ziplistGetFirstElement}, {"test_ziplistGetElementOutOfRangeReverse", test_ziplistGetElementOutOfRangeReverse}, {"test_ziplistIterateThroughFullList", test_ziplistIterateThroughFullList}, {"test_ziplistIterateThroughListFrom1ToEnd", test_ziplistIterateThroughListFrom1ToEnd}, {"test_ziplistIterateThroughListFrom2ToEnd", test_ziplistIterateThroughListFrom2ToEnd}, {"test_ziplistIterateThroughStartOutOfRange", test_ziplistIterateThroughStartOutOfRange}, {"test_ziplistIterateBackToFront", test_ziplistIterateBackToFront}, {"test_ziplistIterateBackToFrontDeletingAllItems", test_ziplistIterateBackToFrontDeletingAllItems}, {"test_ziplistDeleteInclusiveRange0To0", test_ziplistDeleteInclusiveRange0To0}, {"test_ziplistDeleteInclusiveRange0To1", test_ziplistDeleteInclusiveRange0To1}, {"test_ziplistDeleteInclusiveRange1To2", test_ziplistDeleteInclusiveRange1To2}, {"test_ziplistDeleteWithStartIndexOutOfRange", test_ziplistDeleteWithStartIndexOutOfRange}, {"test_ziplistDeleteWithNumOverflow", test_ziplistDeleteWithNumOverflow}, {"test_ziplistDeleteFooWhileIterating", test_ziplistDeleteFooWhileIterating}, {"test_ziplistReplaceWithSameSize", test_ziplistReplaceWithSameSize}, {"test_ziplistReplaceWithDifferentSize", test_ziplistReplaceWithDifferentSize}, {"test_ziplistRegressionTestForOver255ByteStrings", test_ziplistRegressionTestForOver255ByteStrings}, {"test_ziplistRegressionTestDeleteNextToLastEntries", test_ziplistRegressionTestDeleteNextToLastEntries}, {"test_ziplistCreateLongListAndCheckIndices", test_ziplistCreateLongListAndCheckIndices}, {"test_ziplistCompareStringWithZiplistEntries", test_ziplistCompareStringWithZiplistEntries}, {"test_ziplistMergeTest", test_ziplistMergeTest}, {"test_ziplistStressWithRandomPayloadsOfDifferentEncoding", test_ziplistStressWithRandomPayloadsOfDifferentEncoding}, {"test_ziplistCascadeUpdateEdgeCases", test_ziplistCascadeUpdateEdgeCases}, {"test_ziplistInsertEdgeCase", test_ziplistInsertEdgeCase}, {"test_ziplistStressWithVariableSize", test_ziplistStressWithVariableSize}, {"test_BenchmarkziplistFind", test_BenchmarkziplistFind}, {"test_BenchmarkziplistIndex", test_BenchmarkziplistIndex}, {"test_BenchmarkziplistValidateIntegrity", test_BenchmarkziplistValidateIntegrity}, {"test_BenchmarkziplistCompareWithString", test_BenchmarkziplistCompareWithString}, {"test_BenchmarkziplistCompareWithNumber", test_BenchmarkziplistCompareWithNumber}, {"test_ziplistStress__ziplistCascadeUpdate", test_ziplistStress__ziplistCascadeUpdate}, {NULL, NULL}};

src/unit/test_sds.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,3 +328,44 @@ int test_sdsHeaderSizes(int argc, char **argv, int flags) {
328328

329329
return 0;
330330
}
331+
332+
int test_sdssplitargs(int argc, char **argv, int flags) {
333+
UNUSED(argc);
334+
UNUSED(argv);
335+
UNUSED(flags);
336+
337+
int len;
338+
sds *sargv;
339+
340+
sargv = sdssplitargs("Testing one two three", &len);
341+
TEST_ASSERT(4 == len);
342+
TEST_ASSERT(!strcmp("Testing", sargv[0]));
343+
TEST_ASSERT(!strcmp("one", sargv[1]));
344+
TEST_ASSERT(!strcmp("two", sargv[2]));
345+
TEST_ASSERT(!strcmp("three", sargv[3]));
346+
sdsfreesplitres(sargv, len);
347+
348+
sargv = sdssplitargs("", &len);
349+
TEST_ASSERT(0 == len);
350+
TEST_ASSERT(sargv != NULL);
351+
sdsfreesplitres(sargv, len);
352+
353+
sargv = sdssplitargs("\"Testing split strings\" \'Another split string\'", &len);
354+
TEST_ASSERT(2 == len);
355+
TEST_ASSERT(!strcmp("Testing split strings", sargv[0]));
356+
TEST_ASSERT(!strcmp("Another split string", sargv[1]));
357+
sdsfreesplitres(sargv, len);
358+
359+
sargv = sdssplitargs("\"Hello\" ", &len);
360+
TEST_ASSERT(1 == len);
361+
TEST_ASSERT(!strcmp("Hello", sargv[0]));
362+
sdsfreesplitres(sargv, len);
363+
364+
char *binary_string = "\"\\x73\\x75\\x70\\x65\\x72\\x20\\x00\\x73\\x65\\x63\\x72\\x65\\x74\\x20\\x70\\x61\\x73\\x73\\x77\\x6f\\x72\\x64\"";
365+
sargv = sdssplitargs(binary_string, &len);
366+
TEST_ASSERT(1 == len);
367+
TEST_ASSERT(22 == sdslen(sargv[0]));
368+
sdsfreesplitres(sargv, len);
369+
370+
return 0;
371+
}

0 commit comments

Comments
 (0)