Fri, 20 Dec 2024 15:00:31 +0100
rework of properties parser - fixes #529 and resolves #458
src/Makefile | file | annotate | diff | comparison | revisions | |
src/cx/properties.h | file | annotate | diff | comparison | revisions | |
src/properties.c | file | annotate | diff | comparison | revisions | |
tests/Makefile | file | annotate | diff | comparison | revisions | |
tests/test_properties.c | file | annotate | diff | comparison | revisions |
--- a/src/Makefile Fri Dec 20 15:00:05 2024 +0100 +++ b/src/Makefile Fri Dec 20 15:00:31 2024 +0100 @@ -133,7 +133,7 @@ $(build_dir)/properties$(OBJ_EXT): properties.c cx/properties.h \ cx/common.h cx/string.h cx/allocator.h cx/map.h cx/collection.h \ - cx/iterator.h cx/compare.h cx/hash_key.h cx/array_list.h cx/list.h + cx/iterator.h cx/compare.h cx/hash_key.h cx/buffer.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -c $<
--- a/src/cx/properties.h Fri Dec 20 15:00:05 2024 +0100 +++ b/src/cx/properties.h Fri Dec 20 15:00:31 2024 +0100 @@ -39,7 +39,7 @@ #include "common.h" #include "string.h" #include "map.h" -#include "array_list.h" +#include "buffer.h" #include <stdio.h> #include <string.h> @@ -169,39 +169,14 @@ CxPropertiesConfig config; /** - * The text buffer. - */ - const char *text; - - /** - * Size of the text buffer. + * The text input buffer. */ - size_t text_size; - - /** - * Position in the text buffer. - */ - size_t text_pos; + CxBuffer input; /** - * Temporary internal buffer. - */ - char *buf; - - /** - * Size of the internal buffer. + * Internal buffer. */ - size_t buf_size; - - /** - * Capacity of the internal buffer. - */ - size_t buf_capacity; - - /** - * Internal flags. - */ - int flags; + CxBuffer buffer; }; /** @@ -547,7 +522,7 @@ #endif /** - * Specifies stack memory that shall be used by #cxPropertiesFill(). + * Specifies stack memory that shall be used as internal buffer. * * @param prop the properties interface * @param buf a pointer to stack memory
--- a/src/properties.c Fri Dec 20 15:00:05 2024 +0100 +++ b/src/properties.c Fri Dec 20 15:00:31 2024 +0100 @@ -30,8 +30,6 @@ #include <assert.h> -static const int CX_PROPERTIES_FLAG_USE_STACK = 0x01; - const CxPropertiesConfig cx_properties_config_default = { '=', //'\\', @@ -49,60 +47,8 @@ } void cxPropertiesDestroy(CxProperties *prop) { - if (0 == (prop->flags & CX_PROPERTIES_FLAG_USE_STACK)) { - free(prop->buf); - } - prop->buf = NULL; - prop->buf_capacity = prop->buf_size = 0; -} - -static int cx_properties_ensure_buf_capacity(CxProperties *prop, size_t cap) { - if (prop->buf_capacity >= cap) { - return 0; - } - - // not enough capacity - are we on the stack right now? - if ((prop->flags & CX_PROPERTIES_FLAG_USE_STACK) != 0) { - // move to the heap - char *newbuf = malloc(cap); - if (newbuf == NULL) return 1; - memcpy(newbuf, prop->buf, prop->buf_size); - prop->buf = newbuf; - prop->flags &= CX_PROPERTIES_FLAG_USE_STACK; - } else { - // we are on the heap already, reallocate - // this is legit, because realloc() behaves like malloc() when the - // current pointer is NULL - char *newbuf = realloc(prop->buf, cap); - if (newbuf == NULL) return 1; - prop->buf = newbuf; - } - - // store new capacity and return - prop->buf_capacity = cap; - return 0; -} - -static int cx_properties_rescuen_input(CxProperties *prop, size_t len) { - if (cx_properties_ensure_buf_capacity(prop, prop->buf_size + len)) { - return 1; - } - const char *src = prop->text + prop->text_pos; - char *dest = prop->buf + prop->buf_size; - memcpy(dest, src, len); - prop->buf_size += len; - prop->text_pos += len; - return 0; -} - -static int cx_properties_rescue_input(CxProperties *prop) { - // someone fucked around with our integers, exit immediately - if (prop->text_pos > prop->text_size) return 0; - - // determine the bytes needed - size_t len = prop->text_size - prop->text_pos; - - return cx_properties_rescuen_input(prop, len); + cxBufferDestroy(&prop->input); + cxBufferDestroy(&prop->buffer); } int cxPropertiesFilln( @@ -110,10 +56,15 @@ const char *buf, size_t len ) { - if (cx_properties_rescue_input(prop)) return 1; - prop->text = buf; - prop->text_size = len; - prop->text_pos = 0; + if (cxBufferEof(&prop->input)) { + // destroy a possible previously initialized buffer + cxBufferDestroy(&prop->input); + cxBufferInit(&prop->input, (void*) buf, len, + NULL, CX_BUFFER_COPY_ON_WRITE | CX_BUFFER_AUTO_EXTEND); + prop->input.size = len; + } else { + if (cxBufferAppend(buf, 1, len, &prop->input) < len) return -1; + } return 0; } @@ -122,11 +73,7 @@ char *buf, size_t capacity ) { - assert(prop->buf == NULL); - prop->buf = buf; - prop->buf_capacity = capacity; - prop->buf_size = 0; - prop->flags |= CX_PROPERTIES_FLAG_USE_STACK; + cxBufferInit(&prop->buffer, buf, capacity, NULL, CX_BUFFER_COPY_ON_EXTEND); } CxPropertiesStatus cxPropertiesNext( @@ -135,65 +82,42 @@ cxstring *value ) { // check if we have a text buffer - if (prop->text == NULL) { + if (prop->input.space == NULL) { return CX_PROPERTIES_NULL_INPUT; } + + // a pointer to the buffer we want to read from + CxBuffer *current_buffer = &prop->input; + // check if we have rescued data - if (prop->buf_size > 0) { + if (!cxBufferEof(&prop->buffer)) { // check if we can now get a complete line - const char *buf = prop->text + prop->text_pos; - size_t len = prop->text_size - prop->text_pos; - cxstring str = cx_strn(buf, len); - cxstring nl = cx_strchr(str, '\n'); - if(nl.length > 0) { + cxstring input = cx_strn(prop->input.space + prop->input.pos, + prop->input.size - prop->input.pos); + cxstring nl = cx_strchr(input, '\n'); + if (nl.length > 0) { // we add as much data to the rescue buffer as we need // to complete the line - size_t len_until_nl = (size_t)(nl.ptr - buf) + 1; + size_t len_until_nl = (size_t)(nl.ptr - input.ptr) + 1; - if (cx_properties_rescuen_input(prop, len_until_nl)) { + if (cxBufferAppend(input.ptr, 1, + len_until_nl, &prop->buffer) < len_until_nl) { return CX_PROPERTIES_BUFFER_ALLOC_FAILED; } - // the tmp buffer contains exactly one line now - // we use a trick here: we swap the buffers and recurse - const char *orig_text = prop->text; - size_t orig_size = prop->text_size; - prop->text = prop->buf; - prop->text_size = prop->buf_size; - prop->text_pos = 0; - prop->buf_size = 0; - - CxPropertiesStatus result; - result = cxPropertiesNext(prop, key, value); - - // restore original buffer - prop->text = orig_text; - prop->text_size = orig_size; - - // set the position to after the newline - prop->text_pos = len_until_nl; + // advance the position in the input buffer + prop->input.pos += len_until_nl; - // check the result - if (result == CX_PROPERTIES_NO_ERROR) { - // reset the rescue buffer and return with the result - prop->buf_size = 0; - return result; - } else if (result == CX_PROPERTIES_NO_DATA) { - // rescue buffer contained only blanks or comments - // reset the rescue buffer and retry with text buffer - prop->buf_size = 0; - return cxPropertiesNext(prop, key, value); - } else { - // CX_PROPERTIES_INCOMPLETE_DATA is not possible - // so it must have been another error - // do not reset the rescue buffer and return the error - return result; - } + // we now want to read from the rescue buffer + current_buffer = &prop->buffer; } else { - // still not enough data - if (cx_properties_rescue_input(prop)) { + // still not enough data, copy input buffer to internal buffer + if (cxBufferAppend(input.ptr, 1, + input.length, &prop->buffer) < input.length) { return CX_PROPERTIES_BUFFER_ALLOC_FAILED; } + // reset the input buffer (make way for a re-fill) + cxBufferReset(&prop->input); return CX_PROPERTIES_INCOMPLETE_DATA; } } @@ -204,9 +128,9 @@ char delimiter = prop->config.delimiter; // get one line and parse it - while (prop->text_pos < prop->text_size) { - const char *buf = prop->text + prop->text_pos; - size_t len = prop->text_size - prop->text_pos; + while (!cxBufferEof(current_buffer)) { + const char *buf = current_buffer->space + current_buffer->pos; + size_t len = current_buffer->size - current_buffer->pos; /* * First we check if we have at least one line. We also get indices of @@ -235,10 +159,23 @@ } if (c != '\n') { - // we don't have enough data for a line - if (cx_properties_rescue_input(prop)) { + // we don't have enough data for a line, use the rescue buffer + assert(current_buffer != &prop->buffer); + // make sure that the rescue buffer does not already contain something + assert(cxBufferEof(&prop->buffer)); + if (prop->buffer.space == NULL) { + // initialize a rescue buffer, if the user did not provide one + cxBufferInit(&prop->buffer, NULL, 256, NULL, CX_BUFFER_AUTO_EXTEND); + } else { + // from a previous rescue there might be already read data + // reset the buffer to avoid unnecessary buffer extension + cxBufferReset(&prop->buffer); + } + if (cxBufferAppend(buf, 1, len, &prop->buffer) < len) { return CX_PROPERTIES_BUFFER_ALLOC_FAILED; } + // reset the input buffer (make way for a re-fill) + cxBufferReset(&prop->input); return CX_PROPERTIES_INCOMPLETE_DATA; } @@ -256,6 +193,21 @@ } else { return CX_PROPERTIES_INVALID_MISSING_DELIMITER; } + } else { + // skip blank line + // if it was the rescue buffer, return to the original buffer + if (current_buffer == &prop->buffer) { + // assert that the rescue buffer really does not contain more data + assert(current_buffer->pos + i + 1 == current_buffer->size); + // reset the rescue buffer, but don't destroy it! + cxBufferReset(&prop->buffer); + // continue with the input buffer + current_buffer = &prop->input; + } else { + // if it was the input buffer already, just advance the position + current_buffer->pos += i + 1; + } + continue; } } else { cxstring k = cx_strn(buf, delimiter_index); @@ -267,19 +219,21 @@ if (k.length > 0) { *key = k; *value = val; - prop->text_pos += i + 1; - assert(prop->text_pos <= prop->text_size); + current_buffer->pos += i + 1; + assert(current_buffer->pos <= current_buffer->size); return CX_PROPERTIES_NO_ERROR; } else { return CX_PROPERTIES_INVALID_EMPTY_KEY; } } - - prop->text_pos += i + 1; + // unreachable - either we returned or skipped a blank line + assert(false); } // when we come to this point, all data must have been read - assert(prop->text_pos == prop->text_size); + assert(cxBufferEof(&prop->buffer)); + assert(cxBufferEof(&prop->input)); + return CX_PROPERTIES_NO_DATA; } @@ -310,7 +264,7 @@ CxPropertiesSource *src, cxstring *target ) { - if (prop->text == src->src) { + if (prop->input.space == src->src) { // when the input buffer already contains the string // we have nothing more to provide target->length = 0; @@ -351,6 +305,7 @@ CxPropertiesSource src; src.src = (void*) str.ptr; src.data_size = str.length; + src.data_ptr = NULL; src.read_func = cx_properties_read_string; src.read_init_func = NULL; src.read_clean_func = NULL; @@ -361,6 +316,7 @@ CxPropertiesSource src; src.src = (void*) str; src.data_size = len; + src.data_ptr = NULL; src.read_func = cx_properties_read_string; src.read_init_func = NULL; src.read_clean_func = NULL; @@ -371,6 +327,7 @@ CxPropertiesSource src; src.src = (void*) str; src.data_size = strlen(str); + src.data_ptr = NULL; src.read_func = cx_properties_read_string; src.read_init_func = NULL; src.read_clean_func = NULL; @@ -381,6 +338,7 @@ CxPropertiesSource src; src.src = file; src.data_size = chunk_size; + src.data_ptr = NULL; src.read_func = cx_properties_read_file; src.read_init_func = cx_properties_read_init_file; src.read_clean_func = cx_properties_read_clean_file; @@ -420,9 +378,7 @@ } // set the input buffer and read the k/v-pairs - prop->text = input.ptr; - prop->text_size = input.length; - prop->text_pos = 0; + cxPropertiesFill(prop, input); CxPropertiesStatus kv_status; do {
--- a/tests/Makefile Fri Dec 20 15:00:05 2024 +0100 +++ b/tests/Makefile Fri Dec 20 15:00:31 2024 +0100 @@ -121,8 +121,8 @@ ../src/cx/common.h util_allocator.h ../src/cx/allocator.h \ ../src/cx/properties.h ../src/cx/string.h ../src/cx/allocator.h \ ../src/cx/map.h ../src/cx/collection.h ../src/cx/iterator.h \ - ../src/cx/compare.h ../src/cx/hash_key.h ../src/cx/array_list.h \ - ../src/cx/list.h ../src/cx/hash_map.h + ../src/cx/compare.h ../src/cx/hash_key.h ../src/cx/buffer.h \ + ../src/cx/hash_map.h @echo "Compiling $<" $(CC) -o $@ $(CFLAGS) -I../src -c $<
--- a/tests/test_properties.c Fri Dec 20 15:00:05 2024 +0100 +++ b/tests/test_properties.c Fri Dec 20 15:00:31 2024 +0100 @@ -41,9 +41,8 @@ CX_TEST_ASSERT(prop.config.comment1 == '#'); CX_TEST_ASSERT(prop.config.comment2 == 0); CX_TEST_ASSERT(prop.config.comment3 == 0); - CX_TEST_ASSERT(prop.flags == 0); - CX_TEST_ASSERT(prop.text == NULL); - CX_TEST_ASSERT(prop.buf == NULL); + CX_TEST_ASSERT(prop.input.space == NULL); + CX_TEST_ASSERT(prop.buffer.space == NULL); cxPropertiesDestroy(&prop); } @@ -97,9 +96,9 @@ CX_TEST_DO { for (int i = 0; i < 10; i++) { cxPropertiesFill(&prop, tests[i]); - CX_TEST_ASSERT(prop.text == tests[i]); - CX_TEST_ASSERT(prop.text_size == strlen(tests[i])); - CX_TEST_ASSERT(prop.text_pos == 0); + CX_TEST_ASSERT(prop.input.space == tests[i]); + CX_TEST_ASSERT(prop.input.size == strlen(tests[i])); + CX_TEST_ASSERT(prop.input.pos == 0); result = cxPropertiesNext(&prop, &key, &value); cxstring k = cx_str(keys[i]); @@ -203,35 +202,35 @@ CX_TEST_DO { str = ""; - cxPropertiesFill(&prop, str); + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA); str = " \n"; - cxPropertiesFill(&prop, str); + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA); str = "name"; - cxPropertiesFill(&prop, str); + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); str = " "; - cxPropertiesFill(&prop, str); + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); // call fill twice in a row str = "= "; - cxPropertiesFill(&prop, str); + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str)); str = "value"; - cxPropertiesFill(&prop, str); + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); str = "\n"; - cxPropertiesFill(&prop, str); + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); CX_TEST_ASSERT(0 == cx_strcmp(key, cx_str("name"))); @@ -239,17 +238,17 @@ // second round str = "#comment\n"; - cxPropertiesFill(&prop, str); + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA); str = "#comment\nname2 = "; - cxPropertiesFill(&prop, str); + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); str = "value2\na = b\n"; - cxPropertiesFill(&prop, str); + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); CX_TEST_ASSERT(0 == cx_strcmp(key, cx_str("name2"))); @@ -261,17 +260,17 @@ CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("b"))); str = "# comment\n#\n#\ntests = "; - cxPropertiesFill(&prop, str); + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); str = "test1 "; - cxPropertiesFill(&prop, str); + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); str = "test2 test3 test4\n"; - cxPropertiesFill(&prop, str); + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); CX_TEST_ASSERT(0 == cx_strcmp(key, cx_str("tests"))); @@ -279,22 +278,22 @@ // test if cxPropertiesNext finds a name/value after a comment str = "# just a comment"; - cxPropertiesFill(&prop, str); + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); str = " in 3"; - cxPropertiesFill(&prop, str); + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); str = " parts\nx = 1\n"; - cxPropertiesFill(&prop, str); + CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str)); result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); CX_TEST_ASSERT(0 == cx_strcmp(key, cx_str("x"))); CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("1"))); - + // finally we are done result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA); @@ -308,7 +307,7 @@ CxPropertiesStatus result; cxstring key; cxstring value; - + size_t key_len = 512; char *long_key = (char*)malloc(key_len); memset(long_key, 'a', 70); @@ -371,14 +370,13 @@ result = cxPropertiesNext(&prop, &key, &value); CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA); - CX_TEST_ASSERT(prop.buf != NULL); - CX_TEST_ASSERT(prop.buf_capacity > 0); - CX_TEST_ASSERT(prop.buf_size == 0); + CX_TEST_ASSERT(prop.buffer.capacity > 0); + CX_TEST_ASSERT(cxBufferEof(&prop.buffer)); + CX_TEST_ASSERT(cxBufferEof(&prop.input)); cxPropertiesDestroy(&prop); - - CX_TEST_ASSERT(prop.buf == NULL); - CX_TEST_ASSERT(prop.buf_capacity == 0); - CX_TEST_ASSERT(prop.buf_size == 0); + CX_TEST_ASSERT(prop.buffer.capacity == 0); + CX_TEST_ASSERT(prop.buffer.size == 0); + CX_TEST_ASSERT(prop.buffer.pos == 0); } free(long_key); @@ -521,6 +519,105 @@ cx_testing_allocator_destroy(&talloc); } +CX_TEST(test_properties_multiple_fill) { + const char *props1 = "key1 = value1\n"; + const char *props2 = "key2 = value2\n"; + const char *props3 = "key3 = value3\n"; + + CxProperties prop; + cxPropertiesInitDefault(&prop); + CxPropertiesStatus result; + cxstring key; + cxstring value; + CX_TEST_DO { + cxPropertiesFill(&prop, props1); + cxPropertiesFill(&prop, props2); + cxPropertiesFill(&prop, props3); + result = cxPropertiesNext(&prop, &key, &value); + CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); + CX_TEST_ASSERT(0 == cx_strcmp(key, cx_str("key1"))); + CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("value1"))); + result = cxPropertiesNext(&prop, &key, &value); + CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); + CX_TEST_ASSERT(0 == cx_strcmp(key, cx_str("key2"))); + CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("value2"))); + result = cxPropertiesNext(&prop, &key, &value); + CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); + CX_TEST_ASSERT(0 == cx_strcmp(key, cx_str("key3"))); + CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("value3"))); + + result = cxPropertiesNext(&prop, &key, &value); + CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA); + } + cxPropertiesDestroy(&prop); +} + +CX_TEST(test_properties_use_stack) { + const char *props1 = "key1 = val"; + const char *props2 = "ue1\nkey2 = value2"; + const char *props3 = "\nkey3 = value3\n"; + char stackmem[16]; + + CxProperties prop; + cxPropertiesInitDefault(&prop); + cxPropertiesUseStack(&prop, stackmem, 16); + CxPropertiesStatus result; + cxstring key; + cxstring value; + CX_TEST_DO { + cxPropertiesFill(&prop, props1); + result = cxPropertiesNext(&prop, &key, &value); + CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); + cxPropertiesFill(&prop, props2); + result = cxPropertiesNext(&prop, &key, &value); + CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); + CX_TEST_ASSERT(0 == cx_strcmp(key, cx_str("key1"))); + CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("value1"))); + result = cxPropertiesNext(&prop, &key, &value); + CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA); + cxPropertiesFill(&prop, props3); + result = cxPropertiesNext(&prop, &key, &value); + CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); + CX_TEST_ASSERT(0 == cx_strcmp(key, cx_str("key2"))); + CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("value2"))); + result = cxPropertiesNext(&prop, &key, &value); + CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); + CX_TEST_ASSERT(0 == cx_strcmp(key, cx_str("key3"))); + CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("value3"))); + result = cxPropertiesNext(&prop, &key, &value); + CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA); + } + cxPropertiesDestroy(&prop); +} + +CX_TEST(test_properties_empty_key) { + const char *fail1 = "= val\n"; + const char *fail2 = " = val\n"; + const char *good = " key = val\n"; + + CxProperties prop; + CxPropertiesStatus result; + cxstring key; + cxstring value; + CX_TEST_DO { + cxPropertiesInitDefault(&prop); + cxPropertiesFill(&prop, fail1); + result = cxPropertiesNext(&prop, &key, &value); + CX_TEST_ASSERT(result == CX_PROPERTIES_INVALID_EMPTY_KEY); + cxPropertiesReset(&prop); + cxPropertiesFill(&prop, fail2); + result = cxPropertiesNext(&prop, &key, &value); + CX_TEST_ASSERT(result == CX_PROPERTIES_INVALID_EMPTY_KEY); + cxPropertiesReset(&prop); + cxPropertiesFill(&prop, good); + result = cxPropertiesNext(&prop, &key, &value); + CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR); + CX_TEST_ASSERT(0 == cx_strcmp(key, cx_str("key"))); + CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("val"))); + cxPropertiesDestroy(&prop); + } +} + CxTestSuite *cx_test_suite_properties(void) { CxTestSuite *suite = cx_test_suite_new("properties"); @@ -531,6 +628,9 @@ cx_test_register(suite, test_properties_next_long_lines); cx_test_register(suite, test_properties_load_string_to_map); cx_test_register(suite, test_properties_load_file_to_map); + cx_test_register(suite, test_properties_multiple_fill); + cx_test_register(suite, test_properties_use_stack); + cx_test_register(suite, test_properties_empty_key); return suite; }