diff --git a/test/metadata_requirements_test.cpp b/test/metadata_requirements_test.cpp index 3c47d5ae..e2d7d5dd 100644 --- a/test/metadata_requirements_test.cpp +++ b/test/metadata_requirements_test.cpp @@ -24,7 +24,13 @@ #include #include +#include +#include + +#include + #include "dwarfs/compression_metadata_requirements.h" +#include "dwarfs/map_util.h" using namespace dwarfs; @@ -91,3 +97,305 @@ TEST(metadata_requirements, dynamic_test) { "block_size '8' does not meet requirements [16, 1024]"))); } } + +namespace { + +enum class test_enum { foo, bar, baz }; + +struct test_metadata { + test_enum enum_value; + std::string string_value; + int16_t int16_value; + uint32_t uint32_value; +}; + +std::optional parse_enum(folly::dynamic const& value) { + static std::unordered_map const enum_map{ + {"foo", test_enum::foo}, + {"bar", test_enum::bar}, + {"baz", test_enum::baz}}; + return get_optional(enum_map, value.asString()); +} + +std::ostream& operator<<(std::ostream& os, test_enum e) { + switch (e) { + case test_enum::foo: + return os << "foo"; + case test_enum::bar: + return os << "bar"; + case test_enum::baz: + return os << "baz"; + } +} + +} // namespace + +template <> +struct fmt::formatter : ostream_formatter {}; + +class metadata_requirements_test : public ::testing::Test { + public: + void SetUp() override { + req = std::make_unique>(); + req->add_set("enum", &test_metadata::enum_value, parse_enum); + req->add_set("string", &test_metadata::string_value); + req->add_range("int16", &test_metadata::int16_value); + req->add_set("uint32", &test_metadata::uint32_value); + } + + void TearDown() override { req.reset(); } + + std::unique_ptr> req; +}; + +TEST_F(metadata_requirements_test, static_test) { + auto dyn = folly::parseJson(R"({ + "enum": ["set", ["foo"]], + "string": ["set", ["cat", "dog"]], + "int16": ["range", -1024, 1024], + "uint32": ["set", [1, 2, 3, 5]] + })"); + + ASSERT_NO_THROW(req->parse(dyn)); + + test_metadata metadata{ + .enum_value = test_enum::foo, + .string_value = "cat", + .int16_value = 256, + .uint32_value = 5, + }; + + EXPECT_NO_THROW(req->check(metadata)); + + metadata.enum_value = test_enum::bar; + + EXPECT_THAT([&]() { req->check(metadata); }, + ThrowsMessage(testing::HasSubstr( + "enum 'bar' does not meet requirements [foo]"))); + + metadata.enum_value = test_enum::foo; + metadata.string_value = "dog"; + + EXPECT_NO_THROW(req->check(metadata)); + + metadata.string_value = "mouse"; + + EXPECT_THAT([&]() { req->check(metadata); }, + ThrowsMessage(testing::HasSubstr( + "string 'mouse' does not meet requirements [cat, dog]"))); + + metadata.string_value = "cat"; + metadata.int16_value = -1024; + + EXPECT_NO_THROW(req->check(metadata)); + + metadata.int16_value = 1024; + + EXPECT_NO_THROW(req->check(metadata)); + + metadata.int16_value = -1025; + + EXPECT_THAT([&]() { req->check(metadata); }, + ThrowsMessage(testing::HasSubstr( + "int16 '-1025' does not meet requirements [-1024..1024]"))); + + metadata.int16_value = 1025; + + EXPECT_THAT([&]() { req->check(metadata); }, + ThrowsMessage(testing::HasSubstr( + "int16 '1025' does not meet requirements [-1024..1024]"))); + + metadata.int16_value = 0; + metadata.uint32_value = 1; + + EXPECT_NO_THROW(req->check(metadata)); + + metadata.uint32_value = 5; + + EXPECT_NO_THROW(req->check(metadata)); + + metadata.uint32_value = 4; + + EXPECT_THAT([&]() { req->check(metadata); }, + ThrowsMessage(testing::HasSubstr( + "uint32 '4' does not meet requirements [1, 2, 3, 5]"))); +} + +TEST_F(metadata_requirements_test, static_test_unsupported) { + auto dyn = folly::parseJson(R"({ + "enum": ["set", ["foo"]], + "string": ["set", ["cat", "dog"]], + "int16": ["range", -1024, 1024], + "uint32": ["set", [1, 2, 3, 5]], + "strange": ["set", ["foo", "bar"]] + })"); + + EXPECT_THAT([&]() { req->parse(dyn); }, + ThrowsMessage(testing::HasSubstr( + "unsupported metadata requirements: strange"))); +} + +TEST_F(metadata_requirements_test, static_test_less_strict) { + auto dyn = folly::parseJson(R"({ + "enum": ["set", ["foo"]], + "int16": ["range", -1024, 1024] + })"); + + ASSERT_NO_THROW(req->parse(dyn)); + + test_metadata metadata{ + .enum_value = test_enum::foo, + .string_value = "cat", + .int16_value = 256, + .uint32_value = 5, + }; + + EXPECT_NO_THROW(req->check(metadata)); +} + +TEST_F(metadata_requirements_test, static_test_req_error_non_object) { + auto dyn = folly::parseJson(R"([])"); + + EXPECT_THAT([&]() { req->parse(dyn); }, + ThrowsMessage(testing::HasSubstr( + "TypeError: expected dynamic type `object', " + "but had type `array'"))); +} + +TEST_F(metadata_requirements_test, static_test_req_error_non_array) { + auto dyn = folly::parseJson(R"({ + "enum": 42 + })"); + + EXPECT_THAT([&]() { req->parse(dyn); }, + ThrowsMessage(testing::HasSubstr( + "found non-array type for requirement 'enum', " + "got type 'int64'"))); +} + +TEST_F(metadata_requirements_test, static_test_req_error_empty_array) { + auto dyn = folly::parseJson(R"({ + "enum": [] + })"); + + EXPECT_THAT([&]() { req->parse(dyn); }, + ThrowsMessage(testing::HasSubstr( + "unexpected empty value for requirement 'enum'"))); +} + +TEST_F(metadata_requirements_test, static_test_req_error_wrong_type) { + auto dyn = folly::parseJson(R"({ + "enum": [17] + })"); + + EXPECT_THAT([&]() { req->parse(dyn); }, + ThrowsMessage(testing::HasSubstr( + "invalid type '17' for requirement 'enum', " + "expected 'set'"))); +} + +TEST_F(metadata_requirements_test, static_test_req_error_unexpected_type) { + auto dyn = folly::parseJson(R"({ + "enum": ["range"] + })"); + + EXPECT_THAT([&]() { req->parse(dyn); }, + ThrowsMessage(testing::HasSubstr( + "invalid type 'range' for requirement 'enum', " + "expected 'set'"))); +} + +TEST_F(metadata_requirements_test, static_test_req_error_invalid_set1) { + auto dyn = folly::parseJson(R"({ + "enum": ["set"] + })"); + + EXPECT_THAT([&]() { req->parse(dyn); }, + ThrowsMessage(testing::HasSubstr( + "unexpected array size 1 for requirement 'enum', " + "expected 2"))); +} + +TEST_F(metadata_requirements_test, static_test_req_error_invalid_set2) { + auto dyn = folly::parseJson(R"({ + "enum": ["set", 42] + })"); + + EXPECT_THAT([&]() { req->parse(dyn); }, + ThrowsMessage(testing::HasSubstr( + "non-array type argument for requirement 'enum', " + "got 'int64'"))); +} + +TEST_F(metadata_requirements_test, static_test_req_error_empty_set) { + auto dyn = folly::parseJson(R"({ + "enum": ["set", []] + })"); + + EXPECT_THAT([&]() { req->parse(dyn); }, + ThrowsMessage(testing::HasSubstr( + "unexpected empty set for requirement 'enum'"))); +} + +TEST_F(metadata_requirements_test, static_test_req_error_invalid_set3) { + auto dyn = folly::parseJson(R"({ + "enum": ["set", ["grmpf"]] + })"); + + EXPECT_THAT([&]() { req->parse(dyn); }, + ThrowsMessage(testing::HasSubstr( + "no supported values for requirement 'enum'"))); +} + +TEST_F(metadata_requirements_test, static_test_req_error_invalid_set4) { + auto dyn = folly::parseJson(R"({ + "uint32": ["set", ["grmpf"]] + })"); + + EXPECT_THAT([&]() { req->parse(dyn); }, + ThrowsMessage(testing::HasSubstr( + "could not parse set value 'grmpf' for requirement 'uint32': " + "Invalid leading character: \"grmpf\""))); +} + +TEST_F(metadata_requirements_test, static_test_req_set_with_invalid_value) { + auto dyn = folly::parseJson(R"({ + "enum": ["set", ["grmpf", "foo"]] + })"); + + EXPECT_NO_THROW(req->parse(dyn)); +} + +TEST_F(metadata_requirements_test, static_test_req_error_range_invalid1) { + auto dyn = folly::parseJson(R"({ + "int16": ["range"] + })"); + + EXPECT_THAT([&]() { req->parse(dyn); }, + ThrowsMessage(testing::HasSubstr( + "unexpected array size 1 for requirement 'int16', " + "expected 3"))); +} + +TEST_F(metadata_requirements_test, static_test_req_error_range_invalid2) { + auto dyn = folly::parseJson(R"({ + "int16": ["range", "bla", 17] + })"); + + EXPECT_THAT( + [&]() { req->parse(dyn); }, + ThrowsMessage(testing::HasSubstr( + "could not parse minimum value 'bla' for requirement 'int16': " + "Invalid leading character: \"bla\""))); +} + +TEST_F(metadata_requirements_test, static_test_req_error_range_invalid3) { + auto dyn = folly::parseJson(R"({ + "int16": ["range", 18, 17] + })"); + + EXPECT_THAT([&]() { req->parse(dyn); }, + ThrowsMessage(testing::HasSubstr( + "expected minimum '18' to be less than or equal to " + "maximum '17' for requirement 'int16'"))); +}