// Copyright 2024 Google LLC // SPDX-License-Identifier: Apache-2.0 // // Licensed 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 "io/fields.h" #include #include #include #include #include #include "hwy/tests/hwy_gtest.h" namespace gcpp { namespace { #if !HWY_TEST_STANDALONE class FieldsTest : public testing::Test {}; #endif void MaybePrint(const IFields& fields) { if (HWY_IS_DEBUG_BUILD) { fields.Print(); } } template void CheckVectorEqual(const std::vector& a, const std::vector& b) { EXPECT_EQ(a.size(), b.size()); for (size_t i = 0; i < a.size(); ++i) { if constexpr (std::is_base_of_v) { a[i].CheckEqual(b[i]); } else { EXPECT_EQ(a[i], b[i]); } } } enum class Enum : uint32_t { k1 = 1, k3 = 3, k8 = 8, }; HWY_MAYBE_UNUSED bool EnumValid(Enum e) { return e == Enum::k1 || e == Enum::k3 || e == Enum::k8; } // Contains all supported types except IFields and std::vector. struct Nested : public IFields { Nested() : nested_u32(0) {} // for std::vector explicit Nested(uint32_t u32) : nested_u32(u32) {} const char* Name() const override { return "Nested"; } void VisitFields(IFieldsVisitor& visitor) override { visitor(nested_u32); visitor(nested_bool); visitor(nested_vector); visitor(nested_enum); visitor(nested_str); visitor(nested_f); } void CheckEqual(const Nested& n) const { EXPECT_EQ(nested_u32, n.nested_u32); EXPECT_EQ(nested_bool, n.nested_bool); CheckVectorEqual(nested_vector, n.nested_vector); EXPECT_EQ(nested_enum, n.nested_enum); EXPECT_EQ(nested_str, n.nested_str); EXPECT_EQ(nested_f, n.nested_f); } uint32_t nested_u32; // set in ctor bool nested_bool = true; std::vector nested_vector = {1, 2, 3}; Enum nested_enum = Enum::k1; std::string nested_str = "nested"; float nested_f = 1.125f; }; // Contains all supported types. struct OldFields : public IFields { const char* Name() const override { return "OldFields"; } void VisitFields(IFieldsVisitor& visitor) override { visitor(old_str); visitor(old_nested); visitor(old1); visitor(oldi); visitor(oldl); visitor(old_vec_str); visitor(old_vec_nested); visitor(old_f); visitor(old_enum); visitor(old_bool); } // Template allows comparing with NewFields. template void CheckEqual(const Other& n) const { EXPECT_EQ(old_str, n.old_str); old_nested.CheckEqual(n.old_nested); EXPECT_EQ(old1, n.old1); EXPECT_EQ(oldi, n.oldi); EXPECT_EQ(oldl, n.oldl); CheckVectorEqual(old_vec_str, n.old_vec_str); CheckVectorEqual(old_vec_nested, n.old_vec_nested); EXPECT_EQ(old_f, n.old_f); EXPECT_EQ(old_enum, n.old_enum); EXPECT_EQ(old_bool, n.old_bool); } std::string old_str = "old"; Nested old_nested = Nested(0); uint32_t old1 = 1; int32_t oldi = -1; uint64_t oldl = 1234567890123456789; std::vector old_vec_str = {"abc", "1234"}; std::vector old_vec_nested = {Nested(1), Nested(4)}; float old_f = 1.125f; Enum old_enum = Enum::k1; bool old_bool = true; }; // OldFields // Simulates adding new fields of all types to an existing struct. struct NewFields : public IFields { const char* Name() const override { return "NewFields"; } void VisitFields(IFieldsVisitor& visitor) override { visitor(old_str); visitor(old_nested); visitor(old1); visitor(oldi); visitor(oldl); visitor(old_vec_str); visitor(old_vec_nested); visitor(old_f); visitor(old_enum); visitor(old_bool); // Change order of field types relative to OldFields to ensure that works. visitor(new_nested); visitor(new_bool); visitor(new_vec_nested); visitor(new_f); visitor(new_vec_str); visitor(new_enum); visitor(new2); visitor(new_str); visitor(new_i); visitor(new_l); } void CheckEqual(const NewFields& n) const { EXPECT_EQ(old_str, n.old_str); old_nested.CheckEqual(n.old_nested); EXPECT_EQ(old1, n.old1); CheckVectorEqual(old_vec_str, n.old_vec_str); CheckVectorEqual(old_vec_nested, n.old_vec_nested); EXPECT_EQ(old_f, n.old_f); EXPECT_EQ(old_enum, n.old_enum); EXPECT_EQ(old_bool, n.old_bool); new_nested.CheckEqual(n.new_nested); EXPECT_EQ(new_bool, n.new_bool); CheckVectorEqual(new_vec_nested, n.new_vec_nested); EXPECT_EQ(new_f, n.new_f); CheckVectorEqual(new_vec_str, n.new_vec_str); EXPECT_EQ(new_enum, n.new_enum); EXPECT_EQ(new2, n.new2); EXPECT_EQ(new_str, n.new_str); } // Copied from OldFields to match the use case of adding new fields. If we // write an OldFields member, that would change the layout due to its size. std::string old_str = "old"; Nested old_nested = Nested(0); uint32_t old1 = 1; int32_t oldi = -1; uint64_t oldl = 1234567890123456789; std::vector old_vec_str = {"abc", "1234"}; std::vector old_vec_nested = {Nested(1), Nested(4)}; float old_f = 1.125f; Enum old_enum = Enum::k1; bool old_bool = true; Nested new_nested = Nested(999); bool new_bool = false; std::vector new_vec_nested = {Nested(2), Nested(3)}; float new_f = -2.0f; std::vector new_vec_str = {"AB", std::string(), "56789"}; Enum new_enum = Enum::k3; uint32_t new2 = 2; std::string new_str = std::string(); // empty is allowed int32_t new_i = 123456789; uint64_t new_l = 876543210987654321; }; // NewFields // Changes all fields to non-default values. NewFields ModifiedNewFields() { NewFields n; n.old_str = "old2"; n.old_nested = Nested(5); n.old1 = 11; n.old_vec_str = {"abc2", "431", "ZZ"}; n.old_vec_nested = {Nested(9)}; n.old_f = -2.5f; n.old_enum = Enum::k3; n.old_bool = false; n.new_nested = Nested(55); n.new_bool = true; n.new_vec_nested = {Nested(3), Nested(33), Nested(333)}; n.new_f = 4.f; n.new_vec_str = {"4321", "321", "21", "1"}; n.new_enum = Enum::k8; n.new2 = 22; n.new_str = "new and even longer"; n.new_i = 246810121; n.new_l = 1357913579113579135; return n; } using Span = hwy::Span; using ReadResult = IFields::ReadResult; void CheckConsumedAll(const ReadResult& result, size_t storage_size) { EXPECT_NE(0, storage_size); // Ensure we notice failure (pos == 0). EXPECT_EQ(storage_size, result.pos); EXPECT_EQ(0, result.missing_fields); EXPECT_EQ(0, result.extra_u32); } // If we do not change any fields, Write+Read returns the defaults. TEST(FieldsTest, TestNewMatchesDefaults) { NewFields new_fields; const std::vector storage = new_fields.Write(); const ReadResult result = new_fields.Read(Span(storage), 0); CheckConsumedAll(result, storage.size()); NewFields().CheckEqual(new_fields); } // Change fields from default and check that Write+Read returns them again. TEST(FieldsTest, TestRoundTrip) { NewFields new_fields = ModifiedNewFields(); const std::vector storage = new_fields.Write(); NewFields copy; const ReadResult result = copy.Read(Span(storage), 0); CheckConsumedAll(result, storage.size()); new_fields.CheckEqual(copy); } // Refuse to write invalid floats. TEST(FieldsTest, TestInvalidFloat) { NewFields new_fields; new_fields.new_f = std::numeric_limits::infinity(); EXPECT_TRUE(new_fields.Write().empty()); new_fields.new_f = std::numeric_limits::quiet_NaN(); EXPECT_TRUE(new_fields.Write().empty()); } // Refuse to write invalid strings. TEST(FieldsTest, TestInvalidString) { NewFields new_fields; // Four zero bytes new_fields.new_str.assign(4, '\0'); EXPECT_TRUE(new_fields.Write().empty()); // Too long new_fields.new_str.assign(257, 'a'); EXPECT_TRUE(new_fields.Write().empty()); // First byte not ASCII new_fields.new_str.assign("123"); new_fields.new_str[0] = 128; EXPECT_TRUE(new_fields.Write().empty()); // Upper byte in later u32 not ASCII new_fields.new_str.assign("ABCDEFGH"); new_fields.new_str[7] = 255; EXPECT_TRUE(new_fields.Write().empty()); } // Write two structs to the same storage. TEST(FieldsTest, TestMultipleWrite) { const NewFields modified = ModifiedNewFields(); std::vector storage = modified.Write(); const size_t modified_size = storage.size(); const NewFields defaults; defaults.AppendTo(storage); // Start with defaults to ensure Read retrieves the modified values. NewFields modified_copy; const ReadResult result1 = modified_copy.Read(Span(storage), 0); CheckConsumedAll(result1, modified_size); modified.CheckEqual(modified_copy); // Start with modified values to ensure Read retrieves the defaults. NewFields defaults_copy = modified; const ReadResult result2 = defaults_copy.Read(Span(storage), result1.pos); CheckConsumedAll(result2, storage.size()); defaults.CheckEqual(defaults_copy); } // Write old defaults, read old using new code. TEST(FieldsTest, TestNewCodeOldData) { OldFields old_fields; const std::vector storage = old_fields.Write(); // Start with modified old values to ensure old defaults overwrite them. NewFields new_fields = ModifiedNewFields(); const ReadResult result = new_fields.Read(Span(storage), 0); MaybePrint(new_fields); EXPECT_NE(0, result.pos); // did not fail EXPECT_NE(0, result.missing_fields); EXPECT_EQ(0, result.extra_u32); old_fields.CheckEqual(new_fields); // old fields are the same in both } // Write old defaults, ensure new defaults remain unchanged. TEST(FieldsTest, TestNewCodeOldDataNewUnchanged) { OldFields old_fields; const std::vector storage = old_fields.Write(); NewFields new_fields; const ReadResult result = new_fields.Read(Span(storage), 0); MaybePrint(new_fields); EXPECT_NE(0, result.pos); // did not fail EXPECT_NE(0, result.missing_fields); EXPECT_EQ(0, result.extra_u32); NewFields().CheckEqual(new_fields); // new fields match their defaults } // Write new defaults, read using old code. TEST(FieldsTest, TestOldCodeNewData) { NewFields new_fields; const std::vector storage = new_fields.Write(); OldFields old_fields; const ReadResult result = old_fields.Read(Span(storage), 0); MaybePrint(old_fields); EXPECT_NE(0, result.pos); // did not fail EXPECT_EQ(0, result.missing_fields); EXPECT_NE(0, result.extra_u32); EXPECT_EQ(storage.size(), result.pos + result.extra_u32); old_fields.CheckEqual(new_fields); // old fields are the same in both // (Can't check new fields because we only read OldFields) } } // namespace } // namespace gcpp HWY_TEST_MAIN();