# HG changeset patch # User Mike Becker # Date 1736684750 -3600 # Node ID fcd5d86c472f5227498fda66543725fe2a5d527c # Parent 2b83302d595a75b2c7ee8b63eda12aec190cccbe fix wrong status of cxPropertiesLoad() when data is incomplete - fixes #560 diff -r 2b83302d595a -r fcd5d86c472f src/cx/properties.h --- a/src/cx/properties.h Sun Jan 12 13:04:32 2025 +0100 +++ b/src/cx/properties.h Sun Jan 12 13:25:50 2025 +0100 @@ -616,6 +616,11 @@ * the return value will be #CX_PROPERTIES_NO_ERROR. * When the source was consumed but no k/v-pairs were found, the return value * will be #CX_PROPERTIES_NO_DATA. + * In case the source data ends unexpectedly, the #CX_PROPERTIES_INCOMPLETE_DATA + * is returned. In that case you should call this function again with the same + * sink and either an updated source or the same source if the source is able to + * yield the missing data. + * * The other result codes apply, according to their description. * * @param prop the properties interface @@ -626,6 +631,7 @@ * @retval CX_PROPERTIES_READ_FAILED reading from the source failed * @retval CX_PROPERTIES_SINK_FAILED sinking the properties into the sink failed * @retval CX_PROPERTIES_NO_DATA the source did not provide any key/value pairs + * @retval CX_PROPERTIES_INCOMPLETE_DATA the source did not provide enough data * @retval CX_PROPERTIES_INVALID_EMPTY_KEY the properties data contains an illegal empty key * @retval CX_PROPERTIES_INVALID_MISSING_DELIMITER the properties data contains a line without delimiter * @retval CX_PROPERTIES_BUFFER_ALLOC_FAILED an internal allocation was necessary but failed diff -r 2b83302d595a -r fcd5d86c472f src/properties.c --- a/src/properties.c Sun Jan 12 13:04:32 2025 +0100 +++ b/src/properties.c Sun Jan 12 13:25:50 2025 +0100 @@ -360,6 +360,7 @@ // transfer the data from the source to the sink CxPropertiesStatus status; + CxPropertiesStatus kv_status = CX_PROPERTIES_NO_DATA; bool found = false; while (true) { // read input @@ -371,14 +372,23 @@ // no more data - break if (input.length == 0) { - status = found ? CX_PROPERTIES_NO_ERROR : CX_PROPERTIES_NO_DATA; + if (found) { + // something was found, check the last kv_status + if (kv_status == CX_PROPERTIES_INCOMPLETE_DATA) { + status = CX_PROPERTIES_INCOMPLETE_DATA; + } else { + status = CX_PROPERTIES_NO_ERROR; + } + } else { + // nothing found + status = CX_PROPERTIES_NO_DATA; + } break; } // set the input buffer and read the k/v-pairs cxPropertiesFill(prop, input); - CxPropertiesStatus kv_status; do { cxstring key, value; kv_status = cxPropertiesNext(prop, &key, &value); diff -r 2b83302d595a -r fcd5d86c472f tests/test_properties.c --- a/tests/test_properties.c Sun Jan 12 13:04:32 2025 +0100 +++ b/tests/test_properties.c Sun Jan 12 13:25:50 2025 +0100 @@ -519,6 +519,64 @@ cx_testing_allocator_destroy(&talloc); } +CX_TEST(test_properties_load_incomplete) { + CxTestingAllocator talloc; + cx_testing_allocator_init(&talloc); + CxAllocator *alloc = &talloc.base; + CX_TEST_DO { + char buffer[512]; + CxProperties prop; + cxPropertiesInitDefault(&prop); + cxPropertiesUseStack(&prop, buffer, 512); + + CxMap *map = cxHashMapCreateSimple(CX_STORE_POINTERS); + cxDefineAdvancedDestructor(map, cxFree, alloc); + CxPropertiesSink sink = cxPropertiesMapSink(map); + sink.data = alloc; // use the testing allocator + CxPropertiesSource src = cxPropertiesCstrSource("key1 = value1\nkey2 = value2\n\n#comment\n\nkey3"); + CxPropertiesStatus status = cxPropertiesLoad(&prop, sink, src); + + CX_TEST_ASSERT(status == CX_PROPERTIES_INCOMPLETE_DATA); + CX_TEST_ASSERT(cxMapSize(map) == 2); + + char *v1 = cxMapGet(map, "key1"); + char *v2 = cxMapGet(map, "key2"); + char *v3 = cxMapGet(map, "key3"); + + CX_TEST_ASSERTM(v1, "value for key1 not found"); + CX_TEST_ASSERTM(v2, "value for key2 not found"); + CX_TEST_ASSERT(v3 == NULL); + + CX_TEST_ASSERT(!strcmp(v1, "value1")); + CX_TEST_ASSERT(!strcmp(v2, "value2")); + + // provide a source with the remaining data + src = cxPropertiesCstrSource(" = value3\n"); + status = cxPropertiesLoad(&prop, sink, src); + + CX_TEST_ASSERT(status == CX_PROPERTIES_NO_ERROR); + CX_TEST_ASSERT(cxMapSize(map) == 3); + + v1 = cxMapGet(map, "key1"); + v2 = cxMapGet(map, "key2"); + v3 = cxMapGet(map, "key3"); + + CX_TEST_ASSERTM(v1, "value for key1 not found"); + CX_TEST_ASSERTM(v2, "value for key2 not found"); + CX_TEST_ASSERTM(v3, "value for key3 not found"); + + CX_TEST_ASSERT(!strcmp(v1, "value1")); + CX_TEST_ASSERT(!strcmp(v2, "value2")); + CX_TEST_ASSERT(!strcmp(v3, "value3")); + + cxMapFree(map); + cxPropertiesDestroy(&prop); + + CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); + } + cx_testing_allocator_destroy(&talloc); +} + CX_TEST(test_properties_multiple_fill) { const char *props1 = "key1 = value1\n"; const char *props2 = "key2 = value2\n"; @@ -628,6 +686,7 @@ 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_load_incomplete); cx_test_register(suite, test_properties_multiple_fill); cx_test_register(suite, test_properties_use_stack); cx_test_register(suite, test_properties_empty_key);