tests/test_properties.c

Mon, 23 Dec 2024 18:32:25 +0100

author
Mike Becker <universe@uap-core.de>
date
Mon, 23 Dec 2024 18:32:25 +0100
changeset 1053
2e86cf779135
parent 1031
8a90552bba29
permissions
-rw-r--r--

fix missing definition of SSIZE_MAX in windows

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "cx/test.h"
#include "util_allocator.h"

#include "cx/properties.h"
#include "cx/hash_map.h"

CX_TEST(test_properties_init) {
    CxProperties prop;
    CX_TEST_DO {
        cxPropertiesInitDefault(&prop);

        CX_TEST_ASSERT(prop.config.delimiter == '=');
        CX_TEST_ASSERT(prop.config.comment1 == '#');
        CX_TEST_ASSERT(prop.config.comment2 == 0);
        CX_TEST_ASSERT(prop.config.comment3 == 0);
        CX_TEST_ASSERT(prop.input.space == NULL);
        CX_TEST_ASSERT(prop.buffer.space == NULL);

        cxPropertiesDestroy(&prop);
    }
}

CX_TEST(test_properties_next) {
    const char *tests[] = {
        "name = value\n",
        "name=value\n",
        "n=value\n",
        "name=v\n",
        "n=v\n",
        "name = value # comment\n",
        "#comment\nn=v\n",
        "# comment1\n# comment2\n\n    \n\nname = value\n",
        "    name     =      value\n",
        "name = value\n\n"
    };

    const char *keys[] = {
        "name",
        "name",
        "n",
        "name",
        "n",
        "name",
        "n",
        "name",
        "name",
        "name"
    };

    const char *values[] = {
        "value",
        "value",
        "value",
        "v",
        "v",
        "value",
        "v",
        "value",
        "value",
        "value"
    };

    CxProperties prop;
    cxPropertiesInitDefault(&prop);
    CxPropertiesStatus result;
    cxstring key;
    cxstring value;
    CX_TEST_DO {
        for (int i = 0; i < 10; i++) {
            cxPropertiesFill(&prop, tests[i]);
            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]);
            cxstring v = cx_str(values[i]);
            CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR);
            CX_TEST_ASSERT(0 == cx_strcmp(key, k));
            CX_TEST_ASSERT(0 == cx_strcmp(value, v));

            result = cxPropertiesNext(&prop, &key, &value);
            CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA);
        }
    }
    cxPropertiesDestroy(&prop);
}

CX_TEST_SUBROUTINE(test_properties_next_multi_check, CxProperties *prop) {
    const char *keys[] = {
            "a",
            "b",
            "c",
            "uap",
            "name",
            "key1",
            "key2",
            "key3"
    };

    const char *values[] = {
            "a value",
            "b value",
            "core",
            "core",
            "ucx",
            "value1",
            "value2",
            "value3"
    };
    CxPropertiesStatus result;
    cxstring key;
    cxstring value;
    for (int i = 0; i < 8; i++) {
        result = cxPropertiesNext(prop, &key, &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR);
        CX_TEST_ASSERT(!cx_strcmp(key, cx_str(keys[i])));
        CX_TEST_ASSERT(!cx_strcmp(value, cx_str(values[i])));
    }
    result = cxPropertiesNext(prop, &key, &value);
    CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA);
}

CX_TEST(test_properties_next_multi) {
    const char *str = "#\n"
        "# properties\n"
        "# contains key/value pairs\n"
        "#\n"
        "a = a value\n"
        "b = b value\n"
        "c = core\n"
        "\n# test\n"
        "uap = core\n"
        "name = ucx\n"
        "# no = property\n"
        "key1 = value1\n"
        "#key1 = wrong value\n"
        "#key2 = not value 2\n"
        "key2 = value2\n"
        "\n\n\n        \n           key3=value3\n";

    CxProperties prop;
    cxPropertiesInitDefault(&prop);

    CX_TEST_DO {
        CxPropertiesStatus result;
        cxstring key;
        cxstring value;
        result = cxPropertiesNext(&prop, &key, &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_NULL_INPUT);

        // check for C string
        cxPropertiesFill(&prop, str);
        CX_TEST_CALL_SUBROUTINE(test_properties_next_multi_check, &prop);

        // check for cxstring
        cxPropertiesFill(&prop, cx_str(str));
        CX_TEST_CALL_SUBROUTINE(test_properties_next_multi_check, &prop);

        // check for mutstr
        cxPropertiesFill(&prop, cx_mutstr((char*)str));
        CX_TEST_CALL_SUBROUTINE(test_properties_next_multi_check, &prop);
    }
    cxPropertiesDestroy(&prop);
}

CX_TEST(test_properties_next_part) {
    CxProperties prop;
    cxPropertiesInitDefault(&prop);
    CxPropertiesStatus result;
    cxstring key;
    cxstring value;
    const char *str;

    CX_TEST_DO {
        str = "";
        CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str));
        result = cxPropertiesNext(&prop, &key,  &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA);

        str = "  \n";
        CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str));
        result = cxPropertiesNext(&prop, &key,  &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA);

        str = "name";
        CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str));
        result = cxPropertiesNext(&prop, &key,  &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA);

        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 = "= ";
        CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str));
        str = "value";
        CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str));
        result = cxPropertiesNext(&prop, &key,  &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA);

        str = "\n";
        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")));
        CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("value")));

        // second round
        str = "#comment\n";
        CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str));
        result = cxPropertiesNext(&prop, &key,  &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA);

        str = "#comment\nname2 = ";
        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";
        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")));
        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("a")));
        CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("b")));

        str = "# comment\n#\n#\ntests = ";
        CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str));
        result = cxPropertiesNext(&prop, &key,  &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA);

        str = "test1 ";
        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";
        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")));
        CX_TEST_ASSERT(0 == cx_strcmp(value, cx_str("test1 test2 test3 test4")));

        // test if cxPropertiesNext finds a name/value after a comment
        str = "# just a comment";
        CX_TEST_ASSERT(0 == cxPropertiesFill(&prop, str));
        result = cxPropertiesNext(&prop, &key,  &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA);

        str = " in 3";
        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";
        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);
    }
    cxPropertiesDestroy(&prop);
}

CX_TEST(test_properties_next_long_lines) {
    CxProperties prop;
    cxPropertiesInitDefault(&prop);
    CxPropertiesStatus result;
    cxstring key;
    cxstring value;

    size_t key_len = 512;
    char *long_key = (char*)malloc(key_len);
    memset(long_key, 'a', 70);
    memset(long_key + 70, 'b', 242);
    memset(long_key + 312, 'c', 200);

    size_t value_len = 2048;
    char *long_value = (char*)malloc(value_len);
    memset(long_value, 'x', 1024);
    memset(long_value+1024, 'y', 1024);

    CX_TEST_DO {
        cxPropertiesFilln(&prop, long_key, 10);
        result = cxPropertiesNext(&prop, &key, &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA);

        cxPropertiesFilln(&prop, long_key + 10, 202);
        result = cxPropertiesNext(&prop, &key, &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA);

        cxPropertiesFilln(&prop, long_key + 212, 200);
        result = cxPropertiesNext(&prop, &key, &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA);

        cxPropertiesFilln(&prop, long_key + 412, 100);
        result = cxPropertiesNext(&prop, &key, &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA);

        const char *str = " = ";
        cxPropertiesFill(&prop, str);
        result = cxPropertiesNext(&prop, &key, &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA);

        cxPropertiesFilln(&prop, long_value, 512);
        result = cxPropertiesNext(&prop, &key, &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA);

        cxPropertiesFilln(&prop, long_value + 512, 1024);
        result = cxPropertiesNext(&prop, &key, &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA);

        cxPropertiesFilln(&prop, long_value + 1536, 512);
        result = cxPropertiesNext(&prop, &key, &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_INCOMPLETE_DATA);

        str = "\n#comment\nkey = value\n";
        cxPropertiesFill(&prop, str);
        result = cxPropertiesNext(&prop, &key, &value);
        cxstring k = cx_strn(long_key, key_len);
        cxstring v = cx_strn(long_value, value_len);
        CX_TEST_ASSERT(result == CX_PROPERTIES_NO_ERROR);
        CX_TEST_ASSERT(0 == cx_strcmp(key, k));
        CX_TEST_ASSERT(0 == cx_strcmp(value, v));

        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("value")));

        result = cxPropertiesNext(&prop, &key, &value);
        CX_TEST_ASSERT(result == CX_PROPERTIES_NO_DATA);

        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.buffer.capacity == 0);
        CX_TEST_ASSERT(prop.buffer.size == 0);
        CX_TEST_ASSERT(prop.buffer.pos == 0);
    }

    free(long_key);
    free(long_value);
}

CX_TEST(test_properties_load_string_to_map) {
    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);

        const char *str = "key1 = value1\nkey2 = value2\n\n#comment\n\nkey3 = value3\n";

        CxMap *map = cxHashMapCreateSimple(CX_STORE_POINTERS);
        cxDefineAdvancedDestructor(map, cxFree, alloc);
        CxPropertiesSink sink = cxPropertiesMapSink(map);
        sink.data = alloc; // use the testing allocator
        CxPropertiesSource src = cxPropertiesCstrSource(str);
        CxPropertiesStatus status = cxPropertiesLoad(&prop, sink, src);

        CX_TEST_ASSERT(status == CX_PROPERTIES_NO_ERROR);
        CX_TEST_ASSERT(cxMapSize(map) == 3);

        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_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"));

        // second test
        cxMapClear(map);

        str = "\n#comment\n";
        src = cxPropertiesCstrnSource(str, strlen(str));
        status = cxPropertiesLoad(&prop, sink, src);

        CX_TEST_ASSERT(status == CX_PROPERTIES_NO_DATA);
        CX_TEST_ASSERT(cxMapSize(map) == 0);

        str = "key1 = value1\nsyntax error line\n";
        src = cxPropertiesStringSource(cx_str(str));
        status = cxPropertiesLoad(&prop, sink, src);

        CX_TEST_ASSERT(status == CX_PROPERTIES_INVALID_MISSING_DELIMITER);

        // the successfully read k/v-pair is in the map, nevertheless
        CX_TEST_ASSERT(cxMapSize(map) == 1);
        char *v = cxMapGet(map, "key1");
        CX_TEST_ASSERT(!strcmp(v, "value1"));

        cxMapFree(map);
        cxPropertiesDestroy(&prop);

        CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc));
    }
    cx_testing_allocator_destroy(&talloc);
}

CX_TEST(test_properties_load_file_to_map) {
    CxTestingAllocator talloc;
    cx_testing_allocator_init(&talloc);
    CxAllocator *alloc = &talloc.base;
    CX_TEST_DO {
        FILE *f = tmpfile();
        CX_TEST_ASSERTM(f, "test file cannot be opened, test aborted");
        fprintf(f, "# properties file\n\nkey1 = value1\nkey2 = value2\n");
        fprintf(f, "\n\nkey3    = value3\n\n");

        size_t key_len = 512;
        char *long_key = (char *) malloc(key_len);
        memset(long_key, 'k', 512);

        size_t value_len = 2048;
        char *long_value = (char *) malloc(value_len);
        memset(long_value, 'v', 2048);

        fwrite(long_key, 1, key_len, f);
        fprintf(f, "    =    ");
        fwrite(long_value, 1, value_len, f);
        fprintf(f, "                         \n");

        fprintf(f, "\n\n\n\nlast_key = property value\n");

        fflush(f);
        fseek(f, 0, SEEK_SET);

        // preparation of test file complete

        CxMap *map = cxHashMapCreateSimple(CX_STORE_POINTERS);
        cxDefineAdvancedDestructor(map, cxFree, alloc);
        CxProperties prop;
        cxPropertiesInitDefault(&prop);
        CxPropertiesSink sink = cxPropertiesMapSink(map);
        sink.data = alloc; // use the testing allocator
        CxPropertiesSource src = cxPropertiesFileSource(f, 512);
        CxPropertiesStatus status = cxPropertiesLoad(&prop, sink, src);
        fclose(f);

        CX_TEST_ASSERT(status == CX_PROPERTIES_NO_ERROR);
        CX_TEST_ASSERT(cxMapSize(map) == 5);

        char *v1 = cxMapGet(map, "key1");
        char *v2 = cxMapGet(map, cx_str("key2"));
        char *v3 = cxMapGet(map, "key3");
        char *lv = cxMapGet(map, cx_strn(long_key, key_len));
        char *lk = cxMapGet(map, "last_key");

        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_ASSERTM(lv, "value for long key not found");
        CX_TEST_ASSERTM(lk, "value for last_key not found");

        CX_TEST_ASSERT(!strcmp(v1, "value1"));
        CX_TEST_ASSERT(!strcmp(v2, "value2"));
        CX_TEST_ASSERT(!strcmp(v3, "value3"));
        cxstring expected = cx_strn(long_value, value_len);
        cxstring actual = cx_str(lv);
        CX_TEST_ASSERT(!cx_strcmp(expected, actual));
        CX_TEST_ASSERT(!strcmp(lk, "property value"));

        free(long_key);
        free(long_value);
        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";
    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");

    cx_test_register(suite, test_properties_init);
    cx_test_register(suite, test_properties_next);
    cx_test_register(suite, test_properties_next_multi);
    cx_test_register(suite, test_properties_next_part);
    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;
}

mercurial