blob: 05aeae789f8c6bec330f45484b07a9048be89037 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <gtest/gtest.h>
#include <cmath>
#include <jansson.h>
#include "celix/Properties.h"
#include "celix_err.h"
#include "celix_properties.h"
#include "celix_properties_private.h"
#include "celix_stdlib_cleanup.h"
class PropertiesSerializationTestSuite : public ::testing::Test {
public:
PropertiesSerializationTestSuite() { celix_err_resetErrors(); }
};
TEST_F(PropertiesSerializationTestSuite, SaveEmptyPropertiesTest) {
//Given an empty properties object
celix_autoptr(celix_properties_t) props = celix_properties_create();
//And an in-memory stream
celix_autofree char* buf = nullptr;
size_t bufLen = 0;
FILE* stream = open_memstream(&buf, &bufLen);
//When saving the properties to the stream
auto status = celix_properties_saveToStream(props, stream, 0);
ASSERT_EQ(CELIX_SUCCESS, status);
//Then the stream contains an empty JSON object
fclose(stream);
EXPECT_STREQ("{}", buf);
}
TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithSingleValuesTest) {
//Given a properties object with single values
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_set(props, "key1", "value1");
celix_properties_set(props, "key2", "value2");
celix_properties_setLong(props, "key3", 3);
celix_properties_setDouble(props, "key4", 4.0);
celix_properties_setBool(props, "key5", true);
celix_properties_assignVersion(props, "key6", celix_version_create(1, 2, 3, "qualifier"));
//And an in-memory stream
celix_autofree char* buf = nullptr;
size_t bufLen = 0;
FILE* stream = open_memstream(&buf, &bufLen);
//When saving the properties to the stream
auto status = celix_properties_saveToStream(props, stream, 0);
ASSERT_EQ(CELIX_SUCCESS, status);
//Then the stream contains the JSON representation snippets of the properties
fclose(stream);
EXPECT_NE(nullptr, strstr(buf, R"("key1":"value1")")) << "JSON: " << buf;
EXPECT_NE(nullptr, strstr(buf, R"("key2":"value2")")) << "JSON: " << buf;
EXPECT_NE(nullptr, strstr(buf, R"("key3":3)")) << "JSON: " << buf;
EXPECT_NE(nullptr, strstr(buf, R"("key4":4.0)")) << "JSON: " << buf;
EXPECT_NE(nullptr, strstr(buf, R"("key5":true)")) << "JSON: " << buf;
EXPECT_NE(nullptr, strstr(buf, R"("key6":"version<1.2.3.qualifier>")")) << "JSON: " << buf;
//And the buf is a valid JSON object
json_error_t error;
json_t* root = json_loads(buf, 0, &error);
EXPECT_NE(nullptr, root) << "Unexpected JSON error: " << error.text;
json_decref(root);
}
TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithNaNAndInfValuesTest) {
//Given a NAN, INF and -INF value
auto keys = {"NAN", "INF", "-INF"};
for (const auto& key : keys) {
//For every value
//Given a properties object with a NAN, INF or -INF value
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_setDouble(props, key, strtod(key, nullptr));
// Then saving the properties to a string succeeds, but value is not added to the JSON (because JSON does not
// support NAN, INF and -INF)
celix_autofree char* output;
auto status = celix_properties_saveToString(props, 0, &output);
ASSERT_EQ(CELIX_SUCCESS, status);
EXPECT_STREQ("{}", output);
//And saving the properties to a string with the flag CELIX_PROPERTIES_ENCODE_ERROR_ON_NAN_INF fails
celix_err_resetErrors();
char* output2;
status = celix_properties_saveToString(props, CELIX_PROPERTIES_ENCODE_ERROR_ON_NAN_INF, &output2);
EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
//And an error msg is added to celix_err
EXPECT_EQ(1, celix_err_getErrorCount());
}
}
TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithArrayListsContainingNaNAndInfValueTest) {
auto keys = {"NAN", "INF", "-INF"};
for (const auto& key : keys) {
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_autoptr(celix_array_list_t) list = celix_arrayList_createDoubleArray();
celix_arrayList_addDouble(list, strtod(key, nullptr));
celix_properties_assignArrayList(props, key, celix_steal_ptr(list));
// Then saving the properties to a string succeeds, but value is not added to the JSON (because JSON does not
// support NAN, INF and -INF)
celix_autofree char* output;
auto status = celix_properties_saveToString(props, 0, &output);
ASSERT_EQ(CELIX_SUCCESS, status);
EXPECT_STREQ("{}", output);
//And saving the properties to a string with the flag CELIX_PROPERTIES_ENCODE_ERROR_ON_NAN_INF fails
celix_err_resetErrors();
char* output2;
status = celix_properties_saveToString(props, CELIX_PROPERTIES_ENCODE_ERROR_ON_NAN_INF, &output2);
EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
//And an error msg is added to celix_err
EXPECT_EQ(3, celix_err_getErrorCount());
celix_err_resetErrors();
char* output3;
status = celix_properties_saveToString(props, CELIX_PROPERTIES_ENCODE_ERROR_ON_EMPTY_ARRAYS, &output3);
EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
EXPECT_EQ(2, celix_err_getErrorCount());
}
}
TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithArrayListsTest) {
// Given a properties object with array list values
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_array_list_t* list1 = celix_arrayList_createStringArray();
celix_arrayList_addString(list1, "value1");
celix_arrayList_addString(list1, "value2");
celix_properties_assignArrayList(props, "key1", list1);
celix_array_list_t* list2 = celix_arrayList_createLongArray();
celix_arrayList_addLong(list2, 1);
celix_arrayList_addLong(list2, 2);
celix_properties_assignArrayList(props, "key2", list2);
celix_array_list_t* list3 = celix_arrayList_createDoubleArray();
celix_arrayList_addDouble(list3, 1.0);
celix_arrayList_addDouble(list3, 2.0);
celix_properties_assignArrayList(props, "key3", list3);
celix_array_list_t* list4 = celix_arrayList_createBoolArray();
celix_arrayList_addBool(list4, true);
celix_arrayList_addBool(list4, false);
celix_properties_assignArrayList(props, "key4", list4);
celix_array_list_t* list5 = celix_arrayList_createVersionArray();
celix_arrayList_assignVersion(list5, celix_version_create(1, 2, 3, "qualifier"));
celix_arrayList_assignVersion(list5, celix_version_create(4, 5, 6, "qualifier"));
celix_properties_assignArrayList(props, "key5", list5);
// And an in-memory stream
celix_autofree char* buf = nullptr;
size_t bufLen = 0;
FILE* stream = open_memstream(&buf, &bufLen);
// When saving the properties to the stream
auto status = celix_properties_saveToStream(props, stream, 0);
ASSERT_EQ(CELIX_SUCCESS, status);
// Then the stream contains the JSON representation snippets of the properties
fclose(stream);
EXPECT_NE(nullptr, strstr(buf, R"("key1":["value1","value2"])")) << "JSON: " << buf;
EXPECT_NE(nullptr, strstr(buf, R"("key2":[1,2])")) << "JSON: " << buf;
EXPECT_NE(nullptr, strstr(buf, R"("key3":[1.0,2.0])")) << "JSON: " << buf;
EXPECT_NE(nullptr, strstr(buf, R"("key4":[true,false])")) << "JSON: " << buf;
EXPECT_NE(nullptr, strstr(buf, R"("key5":["version<1.2.3.qualifier>","version<4.5.6.qualifier>"])"))
<< "JSON: " << buf;
// And the buf is a valid JSON object
json_error_t error;
json_t* root = json_loads(buf, 0, &error);
EXPECT_NE(nullptr, root) << "Unexpected JSON error: " << error.text;
json_decref(root);
}
TEST_F(PropertiesSerializationTestSuite, SaveEmptyArrayTest) {
//Given a properties object with an empty array list of with el types string, long, double, bool, version
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_assignArrayList(props, "key1", celix_arrayList_createStringArray());
celix_properties_assignArrayList(props, "key2", celix_arrayList_createLongArray());
celix_properties_assignArrayList(props, "key3", celix_arrayList_createDoubleArray());
celix_properties_assignArrayList(props, "key4", celix_arrayList_createBoolArray());
celix_properties_assignArrayList(props, "key5", celix_arrayList_createVersionArray());
EXPECT_EQ(5, celix_properties_size(props));
//When saving the properties to a string
celix_autofree char* output1;
auto status = celix_properties_saveToString(props, 0, &output1);
//Then the save went ok
ASSERT_EQ(CELIX_SUCCESS, status);
//And the output contains an empty JSON object, because empty arrays are treated as unset
EXPECT_STREQ("{}", output1);
//When saving the properties to a string with an error on empty array flag
char* output2;
status = celix_properties_saveToString(props, CELIX_PROPERTIES_ENCODE_ERROR_ON_EMPTY_ARRAYS, &output2);
//Then the save fails, because the empty array generates an error
ASSERT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
//And at least one error message is added to celix_err
EXPECT_GE(celix_err_getErrorCount(), 1);
celix_err_printErrors(stderr, "Test Error: ", "\n");
}
TEST_F(PropertiesSerializationTestSuite, SaveEmptyKeyTest) {
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_setString(props, "", "value");
celix_autofree char* output1;
auto status = celix_properties_saveToString(props, 0, &output1);
ASSERT_EQ(CELIX_SUCCESS, status);
celix_autoptr(celix_properties_t) prop2 = nullptr;
status = celix_properties_loadFromString(output1, 0, &prop2);
ASSERT_EQ(CELIX_SUCCESS, status);
ASSERT_TRUE(celix_properties_equals(props, prop2));
}
TEST_F(PropertiesSerializationTestSuite, SaveJSONPathKeysTest) {
//Given a properties object with jpath keys
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_set(props, "key1", "value1");
celix_properties_set(props, "key2", "value2");
celix_properties_set(props, "object1.key3", "value3");
celix_properties_set(props, "object1.key4", "value4");
celix_properties_set(props, "object2.key5", "value5");
celix_properties_set(props, "object3.object4.key6", "value6");
//And an in-memory stream
celix_autofree char* output;
//When saving the properties to the stream
auto status = celix_properties_saveToString(props, CELIX_PROPERTIES_ENCODE_NESTED_STYLE, &output);
ASSERT_EQ(CELIX_SUCCESS, status);
//Then the stream contains the JSON representation snippets of the properties
EXPECT_NE(nullptr, strstr(output, R"("key1":"value1")")) << "JSON: " << output;
EXPECT_NE(nullptr, strstr(output, R"("key2":"value2")")) << "JSON: " << output;
EXPECT_NE(nullptr, strstr(output, R"("object1":{"key3":"value3","key4":"value4"})")) << "JSON: " << output;
EXPECT_NE(nullptr, strstr(output, R"("object2":{"key5":"value5"})")) << "JSON: " << output;
EXPECT_NE(nullptr, strstr(output, R"("object3":{"object4":{"key6":"value6"}})")) << "JSON: " << output;
//And the buf is a valid JSON object
json_error_t error;
json_auto_t* root = json_loads(output, 0, &error);
EXPECT_NE(nullptr, root) << "Unexpected JSON error: " << error.text;
}
TEST_F(PropertiesSerializationTestSuite, SaveJPathKeysWithCollisionTest) {
// note this tests depends on the key iteration order for properties and
// properties key order is based on hash order of the keys, so this test can change if the string hash map
// implementation changes.
//Given a properties object with jpath keys that collide
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_set(props, "key1.key2.key3", "value1");
celix_properties_set(props, "key1.key2", "value2"); //collision with object "key1/key2/key3" -> overwrite
celix_properties_set(props, "key4.key5.key6.key7", "value4");
celix_properties_set(props, "key4.key5.key6", "value3"); //collision with field "key4/key5/key6/key7" -> overwrite
//When saving the properties to a string
celix_autofree char* output = nullptr;
auto status = celix_properties_saveToString(props, CELIX_PROPERTIES_ENCODE_NESTED_STYLE, &output);
ASSERT_EQ(CELIX_SUCCESS, status);
//Then the stream contains the JSON representation of the properties with the collisions resolved
EXPECT_NE(nullptr, strstr(output, R"({"key1":{"key2":"value2"},"key4":{"key5":{"key6":"value3"}}})"))
<< "JSON: " << output;
//And the buf is a valid JSON object
json_error_t error;
json_t* root = json_loads(output, 0, &error);
EXPECT_NE(nullptr, root) << "Unexpected JSON error: " << error.text;
json_decref(root);
}
TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithNestedEndErrorOnCollisionsFlagsTest) {
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_set(props, "key1", "value1");
celix_properties_set(props, "key2", "value2");
celix_properties_set(props, "object1.key3", "value3");
celix_properties_set(props, "object1.key4", "value4");
celix_properties_set(props, "object2.key5", "value5");
celix_properties_set(props, "object3.object4.key6", "value6");
// And an in-memory stream
celix_autofree char* output;
// When saving the properties to the stream
auto status = celix_properties_saveToString(
props, CELIX_PROPERTIES_ENCODE_NESTED_STYLE | CELIX_PROPERTIES_ENCODE_ERROR_ON_COLLISIONS, &output);
ASSERT_EQ(CELIX_SUCCESS, status);
}
TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithKeyNamesWithDotsTest) {
//Given a properties set with key names with dots
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_set(props, "a.key.name.with.dots", "value1");
celix_properties_set(props, ".keyThatStartsWithDot", "value3");
celix_properties_set(props, "keyThatEndsWithDot.", "value5");
celix_properties_set(props, "keyThatEndsWithDoubleDots..", "value6");
celix_properties_set(props, "key..With..Double..Dots", "value7");
celix_properties_set(props, "object.keyThatEndsWithDot.", "value8");
celix_properties_set(props, "object.keyThatEndsWithDoubleDots..", "value9");
celix_properties_set(props, "object.key..With..Double..Dots", "value10");
//When saving the properties to a string
celix_autofree char* output;
auto status = celix_properties_saveToString(props, CELIX_PROPERTIES_ENCODE_NESTED_STYLE, &output);
ASSERT_EQ(CELIX_SUCCESS, status);
//Then the out contains the JSON representation snippets of the properties
EXPECT_NE(nullptr, strstr(output, R"("a":{"key":{"name":{"with":{"dots":"value1"}}}})")) << "JSON: " << output;
EXPECT_NE(nullptr, strstr(output, R"("keyThatStartsWithDot":"value3")")) << "JSON: " << output;
EXPECT_NE(nullptr, strstr(output, R"("":"value5")")) << "JSON: " << output;
EXPECT_NE(nullptr, strstr(output, R"("":"value6")")) << "JSON: " << output;
EXPECT_NE(nullptr, strstr(output, R"("Dots":"value7")")) << "JSON: " << output;
EXPECT_NE(nullptr, strstr(output, R"("":"value8")")) << "JSON: " << output;
EXPECT_NE(nullptr, strstr(output, R"("":"value9")")) << "JSON: " << output;
EXPECT_NE(nullptr, strstr(output, R"("Dots":"value10")")) << "JSON: " << output;
//And the output is a valid JSON object
json_error_t error;
json_t* root = json_loads(output, 0, &error);
EXPECT_NE(nullptr, root) << "Unexpected JSON error: " << error.text;
//And the structure for (e.g.) value10 is correct
json_t* node = json_object_get(root, "object");
ASSERT_NE(nullptr, node);
ASSERT_TRUE(json_is_object(node));
node = json_object_get(node, "key");
ASSERT_NE(nullptr, node);
ASSERT_TRUE(json_is_object(node));
node = json_object_get(node, "");
ASSERT_NE(nullptr, node);
ASSERT_TRUE(json_is_object(node));
node = json_object_get(node, "With");
ASSERT_NE(nullptr, node);
ASSERT_TRUE(json_is_object(node));
node = json_object_get(node, "");
ASSERT_NE(nullptr, node);
ASSERT_TRUE(json_is_object(node));
node = json_object_get(node, "Double");
ASSERT_NE(nullptr, node);
ASSERT_TRUE(json_is_object(node));
node = json_object_get(node, "");
ASSERT_NE(nullptr, node);
ASSERT_TRUE(json_is_object(node));
node = json_object_get(node, "Dots");
ASSERT_NE(nullptr, node);
ASSERT_TRUE(json_is_string(node));
EXPECT_STREQ("value10", json_string_value(node));
json_decref(root);
}
TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithKeyCollision) {
// note this tests depends on the key iteration order for properties and
// properties key order is based on hash order of the keys, so this test can change if the string hash map
// implementation changes.
//Given a properties that contains keys that will collide with an existing JSON object
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_set(props, "key1.key2.key3", "value1");
celix_properties_set(props, "key1.key2", "value2"); //collision with object "key1.key2" -> overwrite
//When saving the properties to a string
celix_autofree char* output1;
auto status = celix_properties_saveToString(props, CELIX_PROPERTIES_ENCODE_NESTED_STYLE, &output1);
//Then the save succeeds
ASSERT_EQ(CELIX_SUCCESS, status);
// And both keys are serialized (one as a flat key) (flat key name is whitebox knowledge)
EXPECT_NE(nullptr, strstr(output1, R"({"key1":{"key2":"value2"}})")) << "JSON: " << output1;
//When saving the properties to a string with the error on key collision flag
char* output2;
status = celix_properties_saveToString(
props, CELIX_PROPERTIES_ENCODE_NESTED_STYLE | CELIX_PROPERTIES_ENCODE_ERROR_ON_COLLISIONS, &output2);
//Then the save fails, because the keys collide
ASSERT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
//And at least one error message is added to celix_err
EXPECT_GE(celix_err_getErrorCount(), 1);
celix_err_printErrors(stderr, "Test Error: ", "\n");
}
TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithAndWithoutStrictFlagTest) {
//Given a properties set with an empty array list
celix_autoptr(celix_properties_t) props = celix_properties_create();
auto* list = celix_arrayList_createStringArray();
celix_properties_assignArrayList(props, "key1", list);
//When saving the properties to a string without the strict flag
celix_autofree char* output;
auto status = celix_properties_saveToString(props, 0, &output);
//Then the save succeeds
ASSERT_EQ(CELIX_SUCCESS, status);
//When saving the properties to a string with the strict flag
char* output2;
status = celix_properties_saveToString(props, CELIX_PROPERTIES_ENCODE_STRICT, &output2);
//Then the save fails, because the empty array generates an error
ASSERT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
//And at least one error message is added to celix_err
EXPECT_GE(celix_err_getErrorCount(), 1);
celix_err_printErrors(stderr, "Test Error: ", "\n");
}
TEST_F(PropertiesSerializationTestSuite, SavePropertiesWithPrettyPrintTest) {
//Given a properties set with 2 keys
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_set(props, "key1", "value1");
celix_properties_set(props, "key2", "value2");
//When saving the properties to a string with pretty print
celix_autofree char* output;
auto status = celix_properties_saveToString(props, CELIX_PROPERTIES_ENCODE_PRETTY, &output);
//Then the save succeeds
ASSERT_EQ(CELIX_SUCCESS, status);
// And the output contains the JSON representation snippets of the properties with pretty print (2 indent spaces and
// newlines)
auto* expected = "{\n \"key2\": \"value2\",\n \"key1\": \"value1\"\n}";
EXPECT_STREQ(expected, output);
}
TEST_F(PropertiesSerializationTestSuite, SaveWithInvalidStreamTest) {
celix_autoptr(celix_properties_t) properties = celix_properties_create();
celix_properties_set(properties, "key", "value");
// Saving properties with invalid stream will fail
auto status = celix_properties_save(properties, "/non-existing/no/rights/file.json", 0);
EXPECT_EQ(CELIX_FILE_IO_EXCEPTION, status);
EXPECT_EQ(1, celix_err_getErrorCount());
auto* readStream = fopen("/dev/null", "r");
status = celix_properties_saveToStream(properties, readStream, 0);
EXPECT_EQ(CELIX_FILE_IO_EXCEPTION, status);
EXPECT_EQ(2, celix_err_getErrorCount());
fclose(readStream);
celix_err_printErrors(stderr, "Test Error: ", "\n");
}
TEST_F(PropertiesSerializationTestSuite, SaveCxxPropertiesTest) {
// Given a C++ Properties object with 2 keys
celix::Properties props{};
props.set("key1", "value1");
props.set("key2", 42);
props.setVector("key3", std::vector<bool>{}); // empty vector
// When saving the properties to a string
std::string result = props.saveToString();
// Then the result contains the JSON representation snippets of the properties
EXPECT_NE(std::string::npos, result.find("\"key1\":\"value1\""));
EXPECT_NE(std::string::npos, result.find("\"key2\":42"));
// When saving the properties to a string using a flat style
std::string result2 = props.saveToString(celix::Properties::EncodingFlags::FlatStyle);
//The result is equals to a default save
EXPECT_EQ(result, result2);
// When saving the properties to a string using an errors on duplicate key flag
EXPECT_THROW(props.saveToString(celix::Properties::EncodingFlags::Strict),
celix::IllegalArgumentException);
// When saving the properties to a string using combined flags
EXPECT_THROW(props.saveToString(
celix::Properties::EncodingFlags::Pretty | celix::Properties::EncodingFlags::ErrorOnEmptyArrays |
celix::Properties::EncodingFlags::ErrorOnCollisions |
celix::Properties::EncodingFlags::ErrorOnNanInf | celix::Properties::EncodingFlags::NestedStyle),
celix::IllegalArgumentException);
// When saving the properties to an invalid filename location
EXPECT_THROW(props.save("/non-existing/no/rights/file.json"),
celix::IOException);
}
TEST_F(PropertiesSerializationTestSuite, LoadEmptyPropertiesTest) {
//Given an empty JSON object
const char* json = "{}";
//When loading the properties from the stream
celix_autoptr(celix_properties_t) props = nullptr;
auto status = celix_properties_loadFromString(json, 0, &props);
ASSERT_EQ(CELIX_SUCCESS, status);
//Then the properties object is empty
EXPECT_EQ(0, celix_properties_size(props));
}
TEST_F(PropertiesSerializationTestSuite, LoadPropertiesWithSingleValuesTest) {
//Given a JSON object with single values for types string, long, double, bool and version
const char* jsonInput = R"({
"strKey":"strValue",
"longKey":42,
"doubleKey":2.0,
"boolKey":true,
"versionKey":"version<1.2.3.qualifier>"
})";
//And a stream with the JSON object
FILE* stream = fmemopen((void*)jsonInput, strlen(jsonInput), "r");
//When loading the properties from the stream
celix_autoptr(celix_properties_t) props = nullptr;
auto status = celix_properties_loadFromStream(stream, 0, &props);
ASSERT_EQ(CELIX_SUCCESS, status);
//Then the properties object contains the single values
EXPECT_EQ(5, celix_properties_size(props));
EXPECT_STREQ("strValue", celix_properties_getString(props, "strKey"));
EXPECT_EQ(42, celix_properties_getLong(props, "longKey", -1));
EXPECT_DOUBLE_EQ(2.0, celix_properties_getDouble(props, "doubleKey", NAN));
EXPECT_TRUE(celix_properties_getBool(props, "boolKey", false));
auto* v = celix_properties_getVersion(props, "versionKey");
ASSERT_NE(nullptr, v);
celix_autofree char* vStr = celix_version_toString(v);
EXPECT_STREQ("1.2.3.qualifier", vStr);
}
TEST_F(PropertiesSerializationTestSuite, LoadPropertiesWithArrayListsTest) {
//Given a JSON object with array values for types string, long, double, bool and version
const char* jsonInput = R"({
"strArr":["value1","value2"],
"intArr":[1,2],
"realArr":[1.0,2.0],
"boolArr":[true,false],
"versionArr":["version<1.2.3.qualifier>","version<4.5.6.qualifier>"],
"mixedRealAndIntArr1":[1,2.0,2,3.0],
"mixedRealAndIntArr2":[1.0,2,2.0,3]
})";
//And a stream with the JSON object
FILE* stream = fmemopen((void*)jsonInput, strlen(jsonInput), "r");
//When loading the properties from the stream
celix_autoptr(celix_properties_t) props = nullptr;
auto status = celix_properties_loadFromStream(stream, 0, &props);
ASSERT_EQ(CELIX_SUCCESS, status);
//Then the properties object contains the array values
EXPECT_EQ(7, celix_properties_size(props));
//And the string array is correctly loaded
auto* strArr = celix_properties_getArrayList(props, "strArr");
ASSERT_NE(nullptr, strArr);
EXPECT_EQ(CELIX_ARRAY_LIST_ELEMENT_TYPE_STRING, celix_arrayList_getElementType(strArr));
EXPECT_EQ(2, celix_arrayList_size(strArr));
EXPECT_STREQ("value1", celix_arrayList_getString(strArr, 0));
EXPECT_STREQ("value2", celix_arrayList_getString(strArr, 1));
//And the long array is correctly loaded
auto* intArr = celix_properties_getArrayList(props, "intArr");
ASSERT_NE(nullptr, intArr);
EXPECT_EQ(CELIX_ARRAY_LIST_ELEMENT_TYPE_LONG, celix_arrayList_getElementType(intArr));
EXPECT_EQ(2, celix_arrayList_size(intArr));
EXPECT_EQ(1, celix_arrayList_getLong(intArr, 0));
EXPECT_EQ(2, celix_arrayList_getLong(intArr, 1));
//And the double array is correctly loaded
auto* realArr = celix_properties_getArrayList(props, "realArr");
ASSERT_NE(nullptr, realArr);
EXPECT_EQ(CELIX_ARRAY_LIST_ELEMENT_TYPE_DOUBLE, celix_arrayList_getElementType(realArr));
EXPECT_EQ(2, celix_arrayList_size(realArr));
EXPECT_DOUBLE_EQ(1.0, celix_arrayList_getDouble(realArr, 0));
EXPECT_DOUBLE_EQ(2.0, celix_arrayList_getDouble(realArr, 1));
//And the bool array is correctly loaded
auto* boolArr = celix_properties_getArrayList(props, "boolArr");
ASSERT_NE(nullptr, boolArr);
EXPECT_EQ(CELIX_ARRAY_LIST_ELEMENT_TYPE_BOOL, celix_arrayList_getElementType(boolArr));
EXPECT_EQ(2, celix_arrayList_size(boolArr));
EXPECT_TRUE(celix_arrayList_getBool(boolArr, 0));
EXPECT_FALSE(celix_arrayList_getBool(boolArr, 1));
//And the version array is correctly loaded
auto* versionArr = celix_properties_getArrayList(props, "versionArr");
ASSERT_NE(nullptr, versionArr);
EXPECT_EQ(CELIX_ARRAY_LIST_ELEMENT_TYPE_VERSION, celix_arrayList_getElementType(versionArr));
EXPECT_EQ(2, celix_arrayList_size(versionArr));
auto* v1 = celix_arrayList_getVersion(versionArr, 0);
ASSERT_NE(nullptr, v1);
celix_autofree char* v1Str = celix_version_toString(v1);
EXPECT_STREQ("1.2.3.qualifier", v1Str);
auto* v2 = celix_arrayList_getVersion(versionArr, 1);
ASSERT_NE(nullptr, v2);
celix_autofree char* v2Str = celix_version_toString(v2);
EXPECT_STREQ("4.5.6.qualifier", v2Str);
//And the mixed json real and int arrays are correctly loaded as double arrays
auto* mixedRealAndIntArr1 = celix_properties_getArrayList(props, "mixedRealAndIntArr1");
ASSERT_NE(nullptr, mixedRealAndIntArr1);
EXPECT_EQ(CELIX_ARRAY_LIST_ELEMENT_TYPE_DOUBLE, celix_arrayList_getElementType(mixedRealAndIntArr1));
EXPECT_EQ(4, celix_arrayList_size(mixedRealAndIntArr1));
EXPECT_DOUBLE_EQ(1.0, celix_arrayList_getDouble(mixedRealAndIntArr1, 0));
EXPECT_DOUBLE_EQ(2.0, celix_arrayList_getDouble(mixedRealAndIntArr1, 1));
EXPECT_DOUBLE_EQ(2.0, celix_arrayList_getDouble(mixedRealAndIntArr1, 2));
EXPECT_DOUBLE_EQ(3.0, celix_arrayList_getDouble(mixedRealAndIntArr1, 3));
auto* mixedRealAndIntArr2 = celix_properties_getArrayList(props, "mixedRealAndIntArr2");
ASSERT_NE(nullptr, mixedRealAndIntArr2);
EXPECT_EQ(CELIX_ARRAY_LIST_ELEMENT_TYPE_DOUBLE, celix_arrayList_getElementType(mixedRealAndIntArr2));
EXPECT_EQ(4, celix_arrayList_size(mixedRealAndIntArr2));
EXPECT_DOUBLE_EQ(1.0, celix_arrayList_getDouble(mixedRealAndIntArr2, 0));
EXPECT_DOUBLE_EQ(2.0, celix_arrayList_getDouble(mixedRealAndIntArr2, 1));
EXPECT_DOUBLE_EQ(2.0, celix_arrayList_getDouble(mixedRealAndIntArr2, 2));
EXPECT_DOUBLE_EQ(3.0, celix_arrayList_getDouble(mixedRealAndIntArr2, 3));
}
TEST_F(PropertiesSerializationTestSuite, LoadPropertiesWithInvalidInputTest) {
auto invalidInputs = {
R"({)", // invalid JSON (caught by jansson)
R"([])", // unsupported JSON (top level array not supported)
R"(42)", // invalid JSON (caught by jansson)
};
for (auto& invalidInput: invalidInputs) {
//Given an invalid JSON object
FILE* stream = fmemopen((void*)invalidInput, strlen(invalidInput), "r");
//When loading the properties from the stream
celix_autoptr(celix_properties_t) props = nullptr;
auto status = celix_properties_loadFromStream(stream, 0, &props);
//Then loading fails
EXPECT_NE(CELIX_SUCCESS, status);
//And at least one error message is added to celix_err
EXPECT_GE(celix_err_getErrorCount(), 1);
celix_err_printErrors(stderr, "Test Error: ", "\n");
fclose(stream);
}
}
TEST_F(PropertiesSerializationTestSuite, LoadPropertiesWithEmptyArrayTest) {
//Given a JSON object with an empty array
auto* inputJSON = R"({"key1":[]})";
//When loading the properties from string
celix_autoptr(celix_properties_t) props = nullptr;
auto status = celix_properties_loadFromString(inputJSON, 0, &props);
//Then loading succeeds
ASSERT_EQ(CELIX_SUCCESS, status);
//And the properties object is empty, because empty arrays are treated as unset
EXPECT_EQ(0, celix_properties_size(props));
//When loading the properties from string with a strict flag
celix_properties_t* props2;
status = celix_properties_loadFromString(inputJSON, CELIX_PROPERTIES_DECODE_ERROR_ON_EMPTY_ARRAYS, &props2);
//Then loading fails, because the empty array generates an error
ASSERT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
//And at least one error message is added to celix_err
ASSERT_GE(celix_err_getErrorCount(), 1);
}
TEST_F(PropertiesSerializationTestSuite, LoadPropertiesWithNestedObjectsTest) {
// Given a complex JSON object
const char* jsonInput = R"({
"key1":"value1",
"key2":"value2",
"object1": {
"key3":"value3",
"key4":true
},
"object2": {
"key5":5.0
},
"object3":{
"object4":{
"key6":6
}
}
})";
// And a stream with the JSON object
FILE* stream = fmemopen((void*)jsonInput, strlen(jsonInput), "r");
// When loading the properties from the stream
celix_autoptr(celix_properties_t) props = nullptr;
auto status = celix_properties_loadFromStream(stream, 0, &props);
ASSERT_EQ(CELIX_SUCCESS, status);
// Then the properties object contains the nested objects
EXPECT_EQ(6, celix_properties_size(props));
EXPECT_STREQ("value1", celix_properties_getString(props, "key1"));
EXPECT_STREQ("value2", celix_properties_getString(props, "key2"));
EXPECT_STREQ("value3", celix_properties_getString(props, "object1.key3"));
EXPECT_EQ(true, celix_properties_getBool(props, "object1.key4", false));
EXPECT_DOUBLE_EQ(5., celix_properties_getDouble(props, "object2.key5", 0.0));
EXPECT_EQ(6, celix_properties_getLong(props, "object3.object4.key6", 0));
}
TEST_F(PropertiesSerializationTestSuite, LoadPropertiesWithDuplicatesTest) {
// Given a complex JSON object with duplicate keys
const char* jsonInput = R"({
"key":2,
"key":3
})";
// When loading the properties from a string.
celix_autoptr(celix_properties_t) props = nullptr;
auto status = celix_properties_loadFromString(jsonInput, 0, &props);
// Then loading succeeds
ASSERT_EQ(CELIX_SUCCESS, status);
// And the properties object contains the last values of the jpath keys
EXPECT_EQ(1, celix_properties_size(props));
EXPECT_EQ(3, celix_properties_getLong(props, "key", 0));
// When decoding the properties from the stream using a flog that does not allow duplicates
celix_properties_t* props2;
status = celix_properties_loadFromString(jsonInput, CELIX_PROPERTIES_DECODE_ERROR_ON_DUPLICATES, &props2);
// Then loading fails, because of a duplicate key
EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
// And at least one error message is added to celix_err
EXPECT_GE(celix_err_getErrorCount(), 1);
celix_err_printErrors(stderr, "Test Error: ", "\n");
}
TEST_F(PropertiesSerializationTestSuite, LoadPropertiesEscapedDotsTest) {
// Given a complex JSON object with collisions and duplicate keys
// Collisions:
// - object object1/object2 and value object1/object2
// - value key1 in object2 in object1 and value object2/key in object1
// - value object3/key4 and value key4 in object object3
// Duplicate JSON keys:
// - key3
const char* jsonInput = R"({
"object1": {
"object2": {
"key1": "value1"
},
"object2.key2": "value2"
},
"object1.object2" : "value3",
"key3": "value4",
"key3": "value5",
"object3.key4": "value6",
"object3": {
"key4": "value7"
}
})";
// When loading the properties from a string.
celix_autoptr(celix_properties_t) props;
auto status = celix_properties_loadFromString(jsonInput, 0, &props);
// Then loading succeeds
ASSERT_EQ(CELIX_SUCCESS, status);
// And the properties object all the values for the colliding keys and a single (latest) value for the duplicate
// keys
EXPECT_EQ(5, celix_properties_size(props));
EXPECT_STREQ("value1", celix_properties_getString(props, "object1.object2.key1"));
EXPECT_STREQ("value2", celix_properties_getString(props, "object1.object2.key2"));
EXPECT_STREQ("value3", celix_properties_getString(props, "object1.object2"));
EXPECT_STREQ("value5", celix_properties_getString(props, "key3"));
EXPECT_STREQ("value7", celix_properties_getString(props, "object3.key4"));
// When decoding the properties from a string using a flag that allows duplicates
celix_properties_t* props2;
status = celix_properties_loadFromString(jsonInput, CELIX_PROPERTIES_DECODE_ERROR_ON_DUPLICATES, &props2);
// Then loading fails, because of a duplicate key
EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
// And at least one error message is added to celix_err
EXPECT_GE(celix_err_getErrorCount(), 1);
celix_err_printErrors(stderr, "Test Error: ", "\n");
// When decoding the properties from a string using a flag that allows collisions
celix_properties_t* props3;
status = celix_properties_loadFromString(jsonInput, CELIX_PROPERTIES_DECODE_ERROR_ON_COLLISIONS, &props3);
// Then loading fails, because of a collision
EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
// And at least one error message is added to celix_err
EXPECT_GE(celix_err_getErrorCount(), 1);
celix_err_printErrors(stderr, "Test Error: ", "\n");
}
TEST_F(PropertiesSerializationTestSuite, LoadPropertiesWithAndWithoutStrictFlagTest) {
auto invalidInputs = {
R"({"mixedArr":["string", true]})", // Mixed array gives error on strict
R"({"mixedVersionAndStringArr":["version<1.2.3.qualifier>","2.3.4"]})", // Mixed array gives error on strict
R"({"key1":null})", // Null value gives error on strict
R"({"":"value"})", // "" key gives error on strict
R"({"emptyArr":[]})", // Empty array gives error on strict
R"({"key1":"val1", "key1":"val2"})"// Duplicate key gives error on strict
};
for (auto& invalidInput: invalidInputs) {
//Given an invalid JSON object
FILE* stream = fmemopen((void*)invalidInput, strlen(invalidInput), "r");
//When loading the properties from the stream with an empty flags
celix_autoptr(celix_properties_t) props = nullptr;
auto status = celix_properties_loadFromStream(stream, 0, &props);
//Then decoding succeeds, because strict is disabled
ASSERT_EQ(CELIX_SUCCESS, status);
EXPECT_GE(celix_err_getErrorCount(), 0);
//But the properties size is 0 or 1, because the all invalid inputs are ignored, except the duplicate key
auto size = celix_properties_size(props);
EXPECT_TRUE(size == 0 || size == 1);
fclose(stream);
}
for (auto& invalidInput: invalidInputs) {
//Given an invalid JSON object
FILE* stream = fmemopen((void*)invalidInput, strlen(invalidInput), "r");
//When loading the properties from the stream with a strict flag
celix_autoptr(celix_properties_t) props = nullptr;
auto status = celix_properties_loadFromStream(stream, CELIX_PROPERTIES_DECODE_STRICT, &props);
//Then decoding fails
EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
//And at least one error message is added to celix_err
EXPECT_GE(celix_err_getErrorCount(), 1);
celix_err_printErrors(stderr, "Test Error: ", "\n");
fclose(stream);
}
}
TEST_F(PropertiesSerializationTestSuite, LoadPropertiesWithUnsupportedArrayTypesTest) {
auto invalidArrays = {
R"({"objArray":[{"obj1": true}, {"obj2": true}]})", // Array with objects not supported.
R"({"arrayArray":[[1,2], [2,4]]})", // Array with array not supported.
R"({"nullArr":[null,null]})" // Array with null values gives error on strict
};
// Decode with no strict flag, will ignore the unsupported arrays
for (auto& invalidArray : invalidArrays) {
// When loading the properties from the string
celix_autoptr(celix_properties_t) props = nullptr;
auto status = celix_properties_loadFromString(invalidArray, 0, &props);
// Then decoding succeeds, because strict is disabled
ASSERT_EQ(CELIX_SUCCESS, status);
EXPECT_GE(celix_err_getErrorCount(), 0);
// But the properties size is 0, because the all invalid inputs are ignored
EXPECT_EQ(0, celix_properties_size(props));
}
// Decode with strict flag, will fail on unsupported arrays
for (auto& invalidArray : invalidArrays) {
// When loading the properties from the string
celix_autoptr(celix_properties_t) props = nullptr;
auto status = celix_properties_loadFromString(invalidArray, CELIX_PROPERTIES_DECODE_STRICT, &props);
// Then decoding fails
EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
// And at least one error message is added to celix_err
EXPECT_GE(celix_err_getErrorCount(), 1);
celix_err_resetErrors();
// When loading the properties from the CELIX_PROPERTIES_DECODE_ERROR_ON_UNSUPPORTED_ARRAYS flag
celix_properties_t* props2;
status =
celix_properties_loadFromString(invalidArray, CELIX_PROPERTIES_DECODE_ERROR_ON_UNSUPPORTED_ARRAYS, &props2);
// Then decoding fails
EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
// And at least one error message is added to celix_err
EXPECT_GE(celix_err_getErrorCount(), 1);
//celix_err_resetErrors();
celix_err_printErrors(stderr, "Test Error: ", "\n");
}
}
TEST_F(PropertiesSerializationTestSuite, LoadPropertiesWithDotsInTheKeysTest) {
// Given a complex JSON object
const char* jsonInput = R"({
".": "value1",
"keyThatEndsWithDots.": "value2",
"key..With..Double..Dots": "value3",
"object": {
".": "value4",
"keyThatEndsWithDot.": "value5",
"key..With..Double..Dot": "value6"
}
})";
// And a stream with the JSON object
FILE* stream = fmemopen((void*)jsonInput, strlen(jsonInput), "r");
// When loading the properties from the stream
celix_autoptr(celix_properties_t) props = nullptr;
auto status = celix_properties_loadFromStream(stream, 0, &props);
celix_err_printErrors(stderr, "Test Error: ", "\n");
ASSERT_EQ(CELIX_SUCCESS, status);
// Then the properties object contains the nested objects
EXPECT_EQ(6, celix_properties_size(props));
EXPECT_STREQ("value1", celix_properties_getString(props, "."));
EXPECT_STREQ("value2", celix_properties_getString(props, "keyThatEndsWithDots."));
EXPECT_STREQ("value3", celix_properties_getString(props, "key..With..Double..Dots"));
EXPECT_STREQ("value4", celix_properties_getString(props, "object.."));
EXPECT_STREQ("value5", celix_properties_getString(props, "object.keyThatEndsWithDot."));
EXPECT_STREQ("value6", celix_properties_getString(props, "object.key..With..Double..Dot"));
}
TEST_F(PropertiesSerializationTestSuite, LoadPropertiesWithInvalidVersionsTest) {
// Given a JSON object with an invalid version string (<, > not allowed in qualifier)
const auto* jsonInput = R"( {"key":"version<1.2.3.<qualifier>>"} )";
// When loading the properties from the stream
celix_autoptr(celix_properties_t) props = nullptr;
auto status = celix_properties_loadFromString(jsonInput, 0, &props);
// Then loading fails
ASSERT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
// And at least one error message is added to celix_err
EXPECT_GE(celix_err_getErrorCount(), 1);
celix_err_printErrors(stderr, "Test Error: ", "\n");
// Given a JSON object with an invalid version strings, that are not recognized as versions
jsonInput =
R"( {"key1":"version<1.2.3", "key2":"version1.2.3>", "key3":"ver<1.2.3>}", "key4":"celix_version<1.2.3>"} )";
// When loading the properties from the stream
status = celix_properties_loadFromString(jsonInput, 0, &props);
// Then loading succeeds
ASSERT_EQ(CELIX_SUCCESS, status);
// But the values are not recognized as versions
EXPECT_NE(CELIX_PROPERTIES_VALUE_TYPE_VERSION, celix_properties_getType(props, "key1"));
EXPECT_NE(CELIX_PROPERTIES_VALUE_TYPE_VERSION, celix_properties_getType(props, "key2"));
EXPECT_NE(CELIX_PROPERTIES_VALUE_TYPE_VERSION, celix_properties_getType(props, "key3"));
EXPECT_NE(CELIX_PROPERTIES_VALUE_TYPE_VERSION, celix_properties_getType(props, "key4"));
}
TEST_F(PropertiesSerializationTestSuite, LoadWithInvalidStreamTest) {
celix_properties_t* dummyProps = nullptr;
// Loading properties with invalid stream will fail
auto status = celix_properties_load("non_existing_file.json", 0, &dummyProps);
EXPECT_EQ(CELIX_FILE_IO_EXCEPTION, status);
EXPECT_EQ(1, celix_err_getErrorCount());
char* buf = nullptr;
size_t size = 0;
FILE* stream = open_memstream(&buf, &size); // empty stream
status = celix_properties_loadFromStream(stream, 0, &dummyProps);
EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
EXPECT_EQ(2, celix_err_getErrorCount());
fclose(stream);
free(buf);
celix_err_printErrors(stderr, "Test Error: ", "\n");
}
TEST_F(PropertiesSerializationTestSuite, LoadCxxPropertiesTest) {
// Given a JSON object
auto jsonInput = R"({"key1":"value1","key2":42,"key2":43})"; // note duplicate key3
// When loading the properties from the JSON object
auto props = celix::Properties::loadFromString(jsonInput);
// Then the properties object contains the values
EXPECT_EQ(2, props.size());
EXPECT_STREQ("value1", props.get("key1").c_str());
EXPECT_EQ(43, props.getAsLong("key2", -1));
// When loading the properties from the JSON object with a strict flag
EXPECT_THROW(celix::Properties::loadFromString(jsonInput, celix::Properties::DecodeFlags::Strict),
celix::IllegalArgumentException);
// When loading the properties from the JSON object with a flag combined
EXPECT_THROW(
celix::Properties::loadFromString(
jsonInput,
celix::Properties::DecodeFlags::ErrorOnCollisions | celix::Properties::DecodeFlags::ErrorOnDuplicates |
celix::Properties::DecodeFlags::ErrorOnEmptyArrays | celix::Properties::DecodeFlags::ErrorOnEmptyKeys |
celix::Properties::DecodeFlags::ErrorOnUnsupportedArrays |
celix::Properties::DecodeFlags::ErrorOnNullValues | celix::Properties::DecodeFlags::ErrorOnNullValues),
celix::IllegalArgumentException);
EXPECT_THROW(celix::Properties::load2("non_existing_file.json"), celix::IOException);
}
TEST_F(PropertiesSerializationTestSuite, SaveAndLoadFlatProperties) {
// Given a properties object with all possible types (but no empty arrays)
celix_autoptr(celix_properties_t) props = celix_properties_create();
celix_properties_set(props, "single/strKey", "strValue");
celix_properties_setLong(props, "single/longKey", 42);
celix_properties_setDouble(props, "single/doubleKey", 2.0);
celix_properties_setBool(props, "single/boolKey", true);
celix_properties_assignVersion(props, "single/versionKey", celix_version_create(1, 2, 3, "qualifier"));
celix_array_list_t* strArr = celix_arrayList_createStringArray();
celix_arrayList_addString(strArr, "value1");
celix_arrayList_addString(strArr, "value2");
celix_properties_assignArrayList(props, "array/stringArr", strArr);
celix_array_list_t* longArr = celix_arrayList_createLongArray();
celix_arrayList_addLong(longArr, 1);
celix_arrayList_addLong(longArr, 2);
celix_properties_assignArrayList(props, "array/longArr", longArr);
celix_array_list_t* doubleArr = celix_arrayList_createDoubleArray();
celix_arrayList_addDouble(doubleArr, 1.0);
celix_arrayList_addDouble(doubleArr, 2.0);
celix_properties_assignArrayList(props, "array/doubleArr", doubleArr);
celix_array_list_t* boolArr = celix_arrayList_createBoolArray();
celix_arrayList_addBool(boolArr, true);
celix_arrayList_addBool(boolArr, false);
celix_properties_assignArrayList(props, "array/boolArr", boolArr);
celix_array_list_t* versionArr = celix_arrayList_createVersionArray();
celix_arrayList_assignVersion(versionArr, celix_version_create(1, 2, 3, "qualifier"));
celix_arrayList_assignVersion(versionArr, celix_version_create(4, 5, 6, "qualifier"));
celix_properties_assignArrayList(props, "array/versionArr", versionArr);
// When saving the properties to a properties_test.json file
const char* filename = "properties_test.json";
auto status = celix_properties_save(props, filename, CELIX_PROPERTIES_ENCODE_PRETTY);
// Then saving succeeds
ASSERT_EQ(CELIX_SUCCESS, status);
// When loading the properties from the properties_test.json file
celix_autoptr(celix_properties_t) loadedProps = nullptr;
status = celix_properties_load(filename, 0, &loadedProps);
// Then loading succeeds
ASSERT_EQ(CELIX_SUCCESS, status);
// And the loaded properties are equal to the original properties
EXPECT_TRUE(celix_properties_equals(props, loadedProps));
}
TEST_F(PropertiesSerializationTestSuite, SaveAndLoadCxxProperties) {
//Given a filename
std::string filename = "properties_test.json";
//And a Properties object with 1 key
celix::Properties props{};
props.set("key1", "value1");
//When saving the properties to the filename
props.save(filename);
//And reloading the properties from the filename
auto props2 = celix::Properties::load2(filename);
//Then the reloaded properties are equal to the original properties
EXPECT_TRUE(props == props2);
}
TEST_F(PropertiesSerializationTestSuite, KeyCollision) {
celix_autoptr(celix_properties_t) props = celix_properties_create();
// pick keys such that key1 appears before key2 when iterating over the properties
celix_properties_set(props, "a.b.haha.arbifdadfsfa", "value1");
celix_properties_set(props, "a.b.haha", "value2");
celix_autofree char* output = nullptr;
auto status = celix_properties_saveToString(props, CELIX_PROPERTIES_ENCODE_NESTED_STYLE | CELIX_PROPERTIES_ENCODE_ERROR_ON_COLLISIONS,
&output);
EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
celix_autoptr(celix_properties_t) props2 = celix_properties_create();
// pick keys such that key1 appears before key2 when iterating over the properties
celix_properties_set(props2, "a.b.c", "value1");
celix_properties_set(props2, "a.b.c.d", "value2");
status = celix_properties_saveToString(props2, CELIX_PROPERTIES_ENCODE_NESTED_STYLE | CELIX_PROPERTIES_ENCODE_ERROR_ON_COLLISIONS,
&output);
EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status);
status = celix_properties_saveToString(props2, CELIX_PROPERTIES_ENCODE_NESTED_STYLE, &output);
// "a.b.c.d" is silently discarded
EXPECT_STREQ(R"({"a":{"b":{"c":"value1"}}})", output);
std::cout << output << std::endl;
EXPECT_EQ(CELIX_SUCCESS, status);
}