fix wrong status of cxPropertiesLoad() when data is incomplete - fixes #560 default tip

Sun, 12 Jan 2025 13:25:50 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 12 Jan 2025 13:25:50 +0100
changeset 1124
fcd5d86c472f
parent 1123
2b83302d595a

fix wrong status of cxPropertiesLoad() when data is incomplete - fixes #560

src/cx/properties.h file | annotate | diff | comparison | revisions
src/properties.c file | annotate | diff | comparison | revisions
tests/test_properties.c file | annotate | diff | comparison | revisions
--- 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
--- 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);
--- 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);

mercurial