/* vim:set ts=2 sw=2 sts=2 et: */ /** * \author Marcus Holland-Moritz (github@mhxnet.de) * \copyright Copyright (c) Marcus Holland-Moritz * * This file is part of dwarfs. * * dwarfs is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * dwarfs is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with dwarfs. If not, see . */ #include #include #include #include #include #include #include "dwarfs/compression_metadata_requirements.h" #include "dwarfs/map_util.h" using namespace dwarfs; TEST(metadata_requirements, dynamic_test) { std::string requirements = R"({ "compression": ["set", ["lz4", "zstd"]], "block_size": ["range", 16, 1024], "channels": ["set", [1, 2, 4]] })"; std::unique_ptr> req; ASSERT_NO_THROW( req = std::make_unique>( requirements)); { std::string metadata = R"({ "compression": "lz4", "block_size": 256, "channels": 2 })"; EXPECT_NO_THROW(req->check(metadata)); } { std::string metadata = R"({ "compression": "lz4", "foo": "bar", "block_size": 256, "channels": 2 })"; EXPECT_NO_THROW(req->check(metadata)); } { std::string metadata = R"({ "compression": "lzma", "block_size": 256, "channels": 2 })"; EXPECT_THAT( [&] { req->check(metadata); }, ThrowsMessage(testing::HasSubstr( "compression 'lzma' does not meet requirements [lz4, zstd]"))); } { std::string metadata = R"({ "block_size": 256, "channels": 2 })"; EXPECT_THAT([&] { req->check(metadata); }, ThrowsMessage( testing::HasSubstr("missing requirement 'compression'"))); } { std::string metadata = R"({ "compression": "zstd", "block_size": 8, "channels": 2 })"; EXPECT_THAT([&] { req->check(metadata); }, ThrowsMessage(testing::HasSubstr( "block_size '8' does not meet requirements [16, 1024]"))); } { std::string metadata = R"({ "compression": "zstd", "block_size": "foo", "channels": 2 })"; EXPECT_THAT([&] { req->check(metadata); }, ThrowsMessage(testing::HasSubstr( "non-integral type for requirement 'block_size', " "got type 'string'"))); } { std::string metadata = R"({ "compression": 13, "block_size": 256, "channels": 2 })"; EXPECT_THAT([&] { req->check(metadata); }, ThrowsMessage(testing::HasSubstr( "non-string type for requirement 'compression', " "got type 'int64'"))); } { std::string metadata = R"({ "compression": 13, "block_size": 256, "channels": "foo" })"; EXPECT_THAT([&] { req->check(metadata); }, ThrowsMessage(testing::HasSubstr( "non-integral type for requirement 'channels', " "got type 'string'"))); } { std::string metadata = R"({ "compression": 13, "block_size": 256, "channels": 3 })"; EXPECT_THAT([&] { req->check(metadata); }, ThrowsMessage(testing::HasSubstr( "channels '3' does not meet requirements [1, 2, 4]"))); } } TEST(metadata_requirements, dynamic_test_error) { using namespace std::literals::string_literals; using req_type = compression_metadata_requirements; EXPECT_THAT( [&] { req_type tmp(R"([])"s); }, ThrowsMessage(testing::HasSubstr( "metadata requirements must be an object, got type 'array'"))); EXPECT_THAT( [&] { req_type tmp(R"({ "compression": 42 })"s); }, ThrowsMessage(testing::HasSubstr( "requirement 'compression' must be an array, got type 'int64'"))); EXPECT_THAT( [&] { req_type tmp(R"({ "compression": [1] })"s); }, ThrowsMessage(testing::HasSubstr( "requirement 'compression' must be an array of at least 2 elements, " "got only 1"))); EXPECT_THAT( [&] { req_type tmp(R"({ "compression": [1, 2] })"s); }, ThrowsMessage(testing::HasSubstr( "type for requirement 'compression' must be a string, got type " "'int64'"))); EXPECT_THAT( [&] { req_type tmp(R"({ "compression": ["foo", 2] })"s); }, ThrowsMessage( testing::HasSubstr("unsupported requirement type 'foo'"))); EXPECT_THAT( [&] { req_type tmp(R"({ "compression": ["range", 2] })"s); }, ThrowsMessage( testing::HasSubstr("unexpected array size 2 for requirement " "'compression', expected 3"))); EXPECT_THAT( [&] { req_type tmp(R"({ "compression": ["range", "foo", 42] })"s); }, ThrowsMessage(testing::HasSubstr( "could not parse minimum value 'foo' for requirement 'compression': " "Invalid leading character: \"foo\""))); EXPECT_THAT( [&] { req_type tmp(R"({ "compression": ["range", 43, 42] })"s); }, ThrowsMessage(testing::HasSubstr( "expected minimum '43' to be less than or equal to maximum '42' for " "requirement 'compression'"))); EXPECT_THAT( [&] { req_type tmp(R"({ "compression": ["set", 2] })"s); }, ThrowsMessage( testing::HasSubstr("set for requirement 'compression' must be an " "array, got type 'int64'"))); EXPECT_THAT( [&] { req_type tmp(R"({ "compression": ["set", []] })"s); }, ThrowsMessage(testing::HasSubstr( "set for requirement 'compression' must not be empty"))); EXPECT_THAT( [&] { req_type tmp(R"({ "compression": ["set", ["foo", "bar", "foo"]] })"s); }, ThrowsMessage(testing::HasSubstr( "duplicate value 'foo' for requirement 'compression'"))); } 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"; } throw std::runtime_error("invalid enum value"); } } // 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_invalid_set5) { auto dyn = folly::parseJson(R"({ "enum": ["set", ["grmpf", "foo", "foo"]] })"); EXPECT_THAT([&]() { req->parse(dyn); }, ThrowsMessage(testing::HasSubstr( "duplicate value 'foo' for requirement 'enum'"))); } 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'"))); }