From 03fc3ec80351c4edef43fb2a34ce96e2a3d1562e Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 4 Apr 2021 22:51:21 +0200 Subject: [PATCH 1/2] Do not allow write variant of string as local variable To be consitent with read where it's not allowed. --- components/esm/variantimp.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/esm/variantimp.cpp b/components/esm/variantimp.cpp index aeea5017e..2b69923d1 100644 --- a/components/esm/variantimp.cpp +++ b/components/esm/variantimp.cpp @@ -98,6 +98,9 @@ void ESM::VariantStringData::write (ESMWriter& esm, Variant::Format format, VarT if (format==Variant::Format_Info) throw std::runtime_error ("info variables of type string not supported"); + if (format==Variant::Format_Local) + throw std::runtime_error ("local variables of type string not supported"); + // GMST esm.writeHNString ("STRV", mValue); } From dae3f022baf4f33372176a2b784213d1750ddbd6 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 4 Apr 2021 20:15:58 +0200 Subject: [PATCH 2/2] Add tests for ESM::Variant --- apps/openmw_test_suite/CMakeLists.txt | 1 + apps/openmw_test_suite/esm/variant.cpp | 515 +++++++++++++++++++++++++ 2 files changed, 516 insertions(+) create mode 100644 apps/openmw_test_suite/esm/variant.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index a3bb0c6f8..d78cb6955 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -13,6 +13,7 @@ if (GTEST_FOUND AND GMOCK_FOUND) mwdialogue/test_keywordsearch.cpp esm/test_fixed_string.cpp + esm/variant.cpp misc/test_stringops.cpp misc/test_endianness.cpp diff --git a/apps/openmw_test_suite/esm/variant.cpp b/apps/openmw_test_suite/esm/variant.cpp new file mode 100644 index 000000000..c00f73d73 --- /dev/null +++ b/apps/openmw_test_suite/esm/variant.cpp @@ -0,0 +1,515 @@ +#include +#include +#include +#include + +#include +#include + +#include + +namespace +{ + using namespace testing; + using namespace ESM; + + Variant makeVariant(VarType type) + { + Variant v; + v.setType(type); + return v; + } + + Variant makeVariant(VarType type, int value) + { + Variant v; + v.setType(type); + v.setInteger(value); + return v; + } + + TEST(ESMVariantTest, move_constructed_should_have_data) + { + Variant a(int{42}); + const Variant b(std::move(a)); + ASSERT_EQ(b.getInteger(), 42); + } + + TEST(ESMVariantTest, copy_constructed_is_equal_to_source) + { + const Variant a(int{42}); + const Variant b(a); + ASSERT_EQ(a, b); + } + + TEST(ESMVariantTest, copy_constructed_does_not_share_data_with_source) + { + const Variant a(int{42}); + Variant b(a); + b.setInteger(13); + ASSERT_EQ(a.getInteger(), 42); + ASSERT_EQ(b.getInteger(), 13); + } + + TEST(ESMVariantTest, move_assigned_should_have_data) + { + Variant b; + { + Variant a(int{42}); + b = std::move(a); + } + ASSERT_EQ(b.getInteger(), 42); + } + + TEST(ESMVariantTest, copy_assigned_is_equal_to_source) + { + const Variant a(int{42}); + Variant b; + b = a; + ASSERT_EQ(a, b); + } + + TEST(ESMVariantTest, not_equal_is_negation_of_equal) + { + const Variant a(int{42}); + Variant b; + b = a; + ASSERT_TRUE(!(a != b)); + } + + TEST(ESMVariantTest, different_types_are_not_equal) + { + ASSERT_NE(Variant(int{42}), Variant(float{2.7f})); + } + + struct ESMVariantWriteToOStreamTest : TestWithParam> {}; + + TEST_P(ESMVariantWriteToOStreamTest, should_write) + { + const auto [variant, result] = GetParam(); + std::ostringstream s; + s << variant; + ASSERT_EQ(s.str(), result); + } + + INSTANTIATE_TEST_SUITE_P(VariantAsString, ESMVariantWriteToOStreamTest, Values( + std::make_tuple(Variant(), "variant none"), + std::make_tuple(Variant(int{42}), "variant long: 42"), + std::make_tuple(Variant(float{2.7f}), "variant float: 2.7"), + std::make_tuple(Variant(std::string("foo")), "variant string: \"foo\""), + std::make_tuple(makeVariant(VT_Unknown), "variant unknown"), + std::make_tuple(makeVariant(VT_Short, 42), "variant short: 42"), + std::make_tuple(makeVariant(VT_Int, 42), "variant int: 42") + )); + + struct ESMVariantGetTypeTest : Test {}; + + TEST(ESMVariantGetTypeTest, default_constructed_should_return_none) + { + ASSERT_EQ(Variant().getType(), VT_None); + } + + TEST(ESMVariantGetTypeTest, for_constructed_from_int_should_return_long) + { + ASSERT_EQ(Variant(int{}).getType(), VT_Long); + } + + TEST(ESMVariantGetTypeTest, for_constructed_from_float_should_return_float) + { + ASSERT_EQ(Variant(float{}).getType(), VT_Float); + } + + TEST(ESMVariantGetTypeTest, for_constructed_from_lvalue_string_should_return_string) + { + const std::string string; + ASSERT_EQ(Variant(string).getType(), VT_String); + } + + TEST(ESMVariantGetTypeTest, for_constructed_from_rvalue_string_should_return_string) + { + ASSERT_EQ(Variant(std::string{}).getType(), VT_String); + } + + struct ESMVariantGetIntegerTest : Test {}; + + TEST(ESMVariantGetIntegerTest, for_default_constructed_should_throw_exception) + { + ASSERT_THROW(Variant().getInteger(), std::runtime_error); + } + + TEST(ESMVariantGetIntegerTest, for_constructed_from_int_should_return_same_value) + { + const Variant variant(int{42}); + ASSERT_EQ(variant.getInteger(), 42); + } + + TEST(ESMVariantGetIntegerTest, for_constructed_from_float_should_return_casted_to_int) + { + const Variant variant(float{2.7}); + ASSERT_EQ(variant.getInteger(), 2); + } + + TEST(ESMVariantGetIntegerTest, for_constructed_from_string_should_throw_exception) + { + const Variant variant(std::string("foo")); + ASSERT_THROW(variant.getInteger(), std::runtime_error); + } + + TEST(ESMVariantGetFloatTest, for_default_constructed_should_throw_exception) + { + ASSERT_THROW(Variant().getFloat(), std::runtime_error); + } + + TEST(ESMVariantGetFloatTest, for_constructed_from_int_should_return_casted_to_float) + { + const Variant variant(int{42}); + ASSERT_EQ(variant.getFloat(), 42); + } + + TEST(ESMVariantGetFloatTest, for_constructed_from_float_should_return_same_value) + { + const Variant variant(float{2.7f}); + ASSERT_EQ(variant.getFloat(), 2.7f); + } + + TEST(ESMVariantGetFloatTest, for_constructed_from_string_should_throw_exception) + { + const Variant variant(std::string("foo")); + ASSERT_THROW(variant.getFloat(), std::runtime_error); + } + + TEST(ESMVariantGetStringTest, for_default_constructed_should_throw_exception) + { + ASSERT_THROW(Variant().getString(), std::runtime_error); + } + + TEST(ESMVariantGetStringTest, for_constructed_from_int_should_throw_exception) + { + const Variant variant(int{42}); + ASSERT_THROW(variant.getString(), std::runtime_error); + } + + TEST(ESMVariantGetStringTest, for_constructed_from_float_should_throw_exception) + { + const Variant variant(float{2.7}); + ASSERT_THROW(variant.getString(), std::runtime_error); + } + + TEST(ESMVariantGetStringTest, for_constructed_from_string_should_return_same_value) + { + const Variant variant(std::string("foo")); + ASSERT_EQ(variant.getString(), "foo"); + } + + TEST(ESMVariantSetTypeTest, for_unknown_should_reset_data) + { + Variant variant(int{42}); + variant.setType(VT_Unknown); + ASSERT_THROW(variant.getInteger(), std::runtime_error); + } + + TEST(ESMVariantSetTypeTest, for_none_should_reset_data) + { + Variant variant(int{42}); + variant.setType(VT_None); + ASSERT_THROW(variant.getInteger(), std::runtime_error); + } + + TEST(ESMVariantSetTypeTest, for_same_type_should_not_change_value) + { + Variant variant(int{42}); + variant.setType(VT_Long); + ASSERT_EQ(variant.getInteger(), 42); + } + + TEST(ESMVariantSetTypeTest, for_float_replaced_by_int_should_cast_float_to_int) + { + Variant variant(float{2.7f}); + variant.setType(VT_Int); + ASSERT_EQ(variant.getInteger(), 2); + } + + TEST(ESMVariantSetTypeTest, for_string_replaced_by_int_should_set_default_initialized_data) + { + Variant variant(std::string("foo")); + variant.setType(VT_Int); + ASSERT_EQ(variant.getInteger(), 0); + } + + TEST(ESMVariantSetTypeTest, for_default_constructed_replaced_by_float_should_set_default_initialized_value) + { + Variant variant; + variant.setType(VT_Float); + ASSERT_EQ(variant.getInteger(), 0.0f); + } + + TEST(ESMVariantSetTypeTest, for_float_replaced_by_short_should_cast_data_to_int) + { + Variant variant(float{2.7f}); + variant.setType(VT_Short); + ASSERT_EQ(variant.getInteger(), 2); + } + + TEST(ESMVariantSetTypeTest, for_float_replaced_by_long_should_cast_data_to_int) + { + Variant variant(float{2.7f}); + variant.setType(VT_Long); + ASSERT_EQ(variant.getInteger(), 2); + } + + TEST(ESMVariantSetTypeTest, for_int_replaced_by_float_should_cast_data_to_float) + { + Variant variant(int{42}); + variant.setType(VT_Float); + ASSERT_EQ(variant.getFloat(), 42.0f); + } + + TEST(ESMVariantSetTypeTest, for_int_replaced_by_string_should_set_default_initialized_data) + { + Variant variant(int{42}); + variant.setType(VT_String); + ASSERT_EQ(variant.getString(), ""); + } + + TEST(ESMVariantSetIntegerTest, for_default_constructed_should_throw_exception) + { + Variant variant; + ASSERT_THROW(variant.setInteger(42), std::runtime_error); + } + + TEST(ESMVariantSetIntegerTest, for_unknown_should_throw_exception) + { + Variant variant; + variant.setType(VT_Unknown); + ASSERT_THROW(variant.setInteger(42), std::runtime_error); + } + + TEST(ESMVariantSetIntegerTest, for_default_int_should_change_value) + { + Variant variant(int{13}); + variant.setInteger(42); + ASSERT_EQ(variant.getInteger(), 42); + } + + TEST(ESMVariantSetIntegerTest, for_int_should_change_value) + { + Variant variant; + variant.setType(VT_Int); + variant.setInteger(42); + ASSERT_EQ(variant.getInteger(), 42); + } + + TEST(ESMVariantSetIntegerTest, for_short_should_change_value) + { + Variant variant; + variant.setType(VT_Short); + variant.setInteger(42); + ASSERT_EQ(variant.getInteger(), 42); + } + + TEST(ESMVariantSetIntegerTest, for_float_should_change_value) + { + Variant variant(float{2.7f}); + variant.setInteger(42); + ASSERT_EQ(variant.getFloat(), 42.0f); + } + + TEST(ESMVariantSetIntegerTest, for_string_should_throw_exception) + { + Variant variant(std::string{}); + ASSERT_THROW(variant.setInteger(42), std::runtime_error); + } + + TEST(ESMVariantSetFloatTest, for_default_constructed_should_throw_exception) + { + Variant variant; + ASSERT_THROW(variant.setFloat(2.7f), std::runtime_error); + } + + TEST(ESMVariantSetFloatTest, for_unknown_should_throw_exception) + { + Variant variant; + variant.setType(VT_Unknown); + ASSERT_THROW(variant.setFloat(2.7f), std::runtime_error); + } + + TEST(ESMVariantSetFloatTest, for_default_int_should_change_value) + { + Variant variant(int{13}); + variant.setFloat(2.7f); + ASSERT_EQ(variant.getInteger(), 2); + } + + TEST(ESMVariantSetFloatTest, for_int_should_change_value) + { + Variant variant; + variant.setType(VT_Int); + variant.setFloat(2.7f); + ASSERT_EQ(variant.getInteger(), 2); + } + + TEST(ESMVariantSetFloatTest, for_short_should_change_value) + { + Variant variant; + variant.setType(VT_Short); + variant.setFloat(2.7f); + ASSERT_EQ(variant.getInteger(), 2); + } + + TEST(ESMVariantSetFloatTest, for_float_should_change_value) + { + Variant variant(float{2.7f}); + variant.setFloat(3.14f); + ASSERT_EQ(variant.getFloat(), 3.14f); + } + + TEST(ESMVariantSetFloatTest, for_string_should_throw_exception) + { + Variant variant(std::string{}); + ASSERT_THROW(variant.setFloat(2.7f), std::runtime_error); + } + + TEST(ESMVariantSetStringTest, for_default_constructed_should_throw_exception) + { + Variant variant; + ASSERT_THROW(variant.setString("foo"), std::runtime_error); + } + + TEST(ESMVariantSetStringTest, for_unknown_should_throw_exception) + { + Variant variant; + variant.setType(VT_Unknown); + ASSERT_THROW(variant.setString("foo"), std::runtime_error); + } + + TEST(ESMVariantSetStringTest, for_default_int_should_throw_exception) + { + Variant variant(int{13}); + ASSERT_THROW(variant.setString("foo"), std::runtime_error); + } + + TEST(ESMVariantSetStringTest, for_int_should_throw_exception) + { + Variant variant; + variant.setType(VT_Int); + ASSERT_THROW(variant.setString("foo"), std::runtime_error); + } + + TEST(ESMVariantSetStringTest, for_short_should_throw_exception) + { + Variant variant; + variant.setType(VT_Short); + ASSERT_THROW(variant.setString("foo"), std::runtime_error); + } + + TEST(ESMVariantSetStringTest, for_float_should_throw_exception) + { + Variant variant(float{2.7f}); + ASSERT_THROW(variant.setString("foo"), std::runtime_error); + } + + TEST(ESMVariantSetStringTest, for_string_should_change_value) + { + Variant variant(std::string("foo")); + variant.setString("bar"); + ASSERT_EQ(variant.getString(), "bar"); + } + + struct WriteToESMTestCase + { + Variant mVariant; + Variant::Format mFormat; + std::size_t mDataSize {}; + std::size_t mDataHash {}; + }; + + std::string write(const Variant& variant, const Variant::Format format) + { + std::ostringstream out; + ESM::ESMWriter writer; + writer.save(out); + variant.write(writer, format); + writer.close(); + return out.str(); + } + + Variant read(const Variant::Format format, const std::string& data) + { + Variant result; + ESM::ESMReader reader; + reader.open(std::make_shared(data), ""); + result.read(reader, format); + return result; + } + + Variant writeAndRead(const Variant& variant, const Variant::Format format, std::size_t dataSize, std::size_t dataHash) + { + const std::string data = write(variant, format); + EXPECT_EQ(data.size(), dataSize); + EXPECT_EQ(std::hash{}(data), dataHash); + return read(format, data); + } + + struct ESMVariantToESMTest : TestWithParam {}; + + TEST_P(ESMVariantToESMTest, deserialized_is_equal_to_serialized) + { + const auto param = GetParam(); + const auto result = writeAndRead(param.mVariant, param.mFormat, param.mDataSize, param.mDataHash); + ASSERT_EQ(param.mVariant, result); + } + + INSTANTIATE_TEST_SUITE_P(VariantAndData, ESMVariantToESMTest, Values( + WriteToESMTestCase {Variant(), Variant::Format_Gmst, 324, 10398667754238537314ul}, + WriteToESMTestCase {Variant(int{42}), Variant::Format_Global, 345, 2440845426097842853ul}, + WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Global, 345, 8428720798053904009ul}, + WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Info, 336, 11930997575130354755ul}, + WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Local, 336, 11930997575130354755ul}, + WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Global, 345, 7812065815960720679ul}, + WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Local, 334, 5017869102981712080ul}, + WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Info, 336, 12560431547347287906ul}, + WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Local, 336, 12560431547347287906ul} + )); + + struct ESMVariantToESMNoneTest : TestWithParam {}; + + TEST_P(ESMVariantToESMNoneTest, deserialized_is_none) + { + const auto param = GetParam(); + const auto result = writeAndRead(param.mVariant, param.mFormat, param.mDataSize, param.mDataHash); + ASSERT_EQ(Variant(), result); + } + + INSTANTIATE_TEST_SUITE_P(VariantAndData, ESMVariantToESMNoneTest, Values( + WriteToESMTestCase {Variant(float{2.7f}), Variant::Format_Gmst, 336, 11930997575130354755ul}, + WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Gmst, 335, 7604528240659685057ul}, + WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Gmst, 336, 12560431547347287906ul} + )); + + struct ESMVariantWriteToESMFailTest : TestWithParam {}; + + TEST_P(ESMVariantWriteToESMFailTest, write_is_not_supported) + { + const auto param = GetParam(); + std::ostringstream out; + ESM::ESMWriter writer; + writer.save(out); + ASSERT_THROW(param.mVariant.write(writer, param.mFormat), std::runtime_error); + } + + INSTANTIATE_TEST_SUITE_P(VariantAndFormat, ESMVariantWriteToESMFailTest, Values( + WriteToESMTestCase {Variant(), Variant::Format_Global}, + WriteToESMTestCase {Variant(), Variant::Format_Info}, + WriteToESMTestCase {Variant(), Variant::Format_Local}, + WriteToESMTestCase {Variant(int{42}), Variant::Format_Gmst}, + WriteToESMTestCase {Variant(int{42}), Variant::Format_Info}, + WriteToESMTestCase {Variant(int{42}), Variant::Format_Local}, + WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Global}, + WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Info}, + WriteToESMTestCase {Variant(std::string("foo")), Variant::Format_Local}, + WriteToESMTestCase {makeVariant(VT_Unknown), Variant::Format_Global}, + WriteToESMTestCase {makeVariant(VT_Int, 42), Variant::Format_Global}, + WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Gmst}, + WriteToESMTestCase {makeVariant(VT_Short, 42), Variant::Format_Info} + )); +}