// // properties_test.cpp // logger-test // // Created by Sam Jaffe on 8/12/20. // #include "logger/properties.h" #include #include "logger/exception.h" namespace logging { void indent(int i, std::ostream * os) { while (i-- > 0) { (*os) << ' ' << ' '; } } void PrintTo(properties const & props, std::ostream * os, int level = 1) { switch (props.data.index()) { case 0: (*os) << '{' << '\n'; for (auto & [k, v] : props.object()) { indent(level + 1, os); (*os) << k << ':' << ' '; PrintTo(v, os, level + 1); } (*os) << '\n'; indent(level, os); (*os) << '}' << '\n'; break; case 1: (*os) << '[' << '\n'; for (auto & v : props.array()) { indent(level + 1, os); PrintTo(v, os, level + 1); } (*os) << '\n'; indent(level, os); (*os) << ']' << '\n'; break; case 2: (*os) << props.str(); break; case 3: (*os) << int(props); break; case 4: (*os) << std::boolalpha << bool(props); break; default: (*os) << "null"; break; } } } MATCHER(Valid, "") { return arg.data.valid(); } MATCHER_P(Contains, key, "") { return arg.contains(key); } MATCHER_P(IsOfType, type, "") { return arg.data.template is(); } MATCHER_P(ArraySizeIs, size, "") { return arg.data.template is() && arg.array().size() == size; } template auto IsOfType() { return IsOfType(T()); } using namespace logging; using namespace ::logging::property; TEST(PropertiesTest, DefaultPropertyIsInvalidType) { EXPECT_THAT(properties(), testing::Not(Valid())); } TEST(PropertiesTest, CanStoreBoolean) { EXPECT_THAT(_v(true), IsOfType()); EXPECT_THAT(_v(true), Valid()); } TEST(PropertiesTest, CanStoreInteger) { EXPECT_THAT(_v(0), IsOfType()); EXPECT_THAT(_v(0), Valid()); } TEST(PropertiesTest, CanStoreString) { EXPECT_THAT(_v("hello"), IsOfType()); EXPECT_THAT(_v("hello"), Valid()); } TEST(PropertiesTest, CanStoreArray) { EXPECT_THAT(_arr({}), IsOfType()); EXPECT_THAT(_arr({}), Valid()); } TEST(PropertiesTest, CanStoreObject) { EXPECT_THAT(_obj({}), IsOfType()); EXPECT_THAT(_obj({}), Valid()); } TEST(PropertiesTest, ArrContainsReturnsTrueEvenWhenUnderlyingIsInvalid) { properties const props = _arr({{}}); EXPECT_THAT(props, Contains(0)); EXPECT_THAT(props[0ul], testing::Not(Valid())); } TEST(PropertiesTest, ObjContainsReturnsTrueEvenWhenUnderlyingIsInvalid) { properties const props = _obj({{"data", {}}}); EXPECT_THAT(props, Contains("data")); EXPECT_THAT(props["data"], testing::Not(Valid())); } TEST(PropertiesTest, ContainsAlwaysFalseForNonContainer) { EXPECT_THAT(_v(true), testing::Not(Contains(0))); EXPECT_THAT(_v(true), testing::Not(Contains(""))); EXPECT_THAT(_v(0), testing::Not(Contains(0))); EXPECT_THAT(_v(0), testing::Not(Contains(""))); EXPECT_THAT(_v(""), testing::Not(Contains(0))); EXPECT_THAT(_v(""), testing::Not(Contains(""))); } TEST(PropertiesTest, ArrayCannotContainKey) { properties const props = _arr({{}}); EXPECT_THAT(props, testing::Not(Contains(""))); } TEST(PropertiesTest, ObjectCannotContainIndex) { properties const props = _obj({{"data", {}}}); EXPECT_THAT(props, testing::Not(Contains(0))); } TEST(PropertiesTest, MistypingWillThrow) { properties prop; // Avoid most vexing parse/unused variable EXPECT_THROW((void)bool(prop), invalid_property_type); EXPECT_THROW((void)int(prop), invalid_property_type); EXPECT_THROW(prop.str(), invalid_property_type); EXPECT_THROW(prop.array(), invalid_property_type); EXPECT_THROW(prop.object(), invalid_property_type); } TEST(PropertiesTest, IndexingNonContainerWillThrow) { properties prop; EXPECT_THROW(prop[0ul], invalid_property_type); EXPECT_THROW(prop[""], invalid_property_type); } TEST(PropertiesTest, IndexingContainerWithWrongTypeWillThrow) { EXPECT_THROW(_obj({{"", {}}})[0ul], invalid_property_type); EXPECT_THROW(_arr({{}})[""], invalid_property_type); } TEST(PropertiesTest, IndexingNonExistantPathWillThrouw) { EXPECT_THROW(_obj({{"", {}}})["data"], missing_property); EXPECT_THROW(_arr({{}})[1], missing_property); } properties const EXAMPLE_PROPERTIES = _obj({{"data", _obj({{"files", _arr({_v("a.txt")})}, {"config", _obj({{"append", _v(false)}})}})}}); TEST(PropertiesTest, MergeWithWillNotAddNewProperties) { properties const input = _obj({{"data", _obj({{"config", _obj({{"compact", _v(true)}})}})}}); properties const output = EXAMPLE_PROPERTIES.mergedWith(input); EXPECT_THAT(output, Contains("data")); EXPECT_THAT(output["data"], Contains("files")); EXPECT_THAT(output["data"]["files"], ArraySizeIs(1)); EXPECT_THAT(output["data"], Contains("config")); EXPECT_THAT(output["data"]["config"], Contains("append")); EXPECT_THAT(output["data"]["config"], testing::Not(Contains("compact"))); } TEST(PropertiesTest, MergeWithWillOverwriteProperties) { properties const input = _obj({{"data", _obj({{"config", _obj({{"append", _v(true)}})}})}}); properties const output = EXAMPLE_PROPERTIES.mergedWith(input); EXPECT_THAT(output, Contains("data")); EXPECT_THAT(output["data"], Contains("files")); EXPECT_THAT(output["data"]["files"], ArraySizeIs(1)); EXPECT_THAT(output["data"], Contains("config")); EXPECT_THAT(output["data"]["config"], Contains("append")); EXPECT_TRUE(output["data"]["config"]["append"]); } TEST(PropertiesTest, ArraysWillBeOverwritten) { properties const input = _obj({{"data", _obj({{"files", _arr({_v("b.txt"), _v("c.txt")})}})}}); properties const output = EXAMPLE_PROPERTIES.mergedWith(input); EXPECT_THAT(output, Contains("data")); EXPECT_THAT(output["data"], Contains("files")); EXPECT_THAT(output["data"]["files"], ArraySizeIs(2)); EXPECT_THAT(output["data"]["files"][0ul].str(), "b.txt"); EXPECT_THAT(output["data"]["files"][1ul].str(), "c.txt"); }