/* * Copyright 2021 Daniel R. Carvalho * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer; * redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution; * neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "base/compiler.hh" #include "base/gtest/cur_tick_fake.hh" #include "base/gtest/logging.hh" #include "base/gtest/serialization_fixture.hh" #include "sim/serialize.hh" using namespace gem5; // Instantiate the mock class to have a valid curTick of 0 GTestTickHandler tickHandler; /** @return Whether the given dir exists and is accessible. */ bool dirExists(std::string dir) { struct stat info; return (stat(dir.c_str(), &info) == 0) && (info.st_mode & S_IFDIR); } using SerializeFixture = SerializationFixture; class CheckpointInFixture : public SerializationFixture { public: std::unique_ptr cpt; using SerializeFixture::SerializeFixture; void SetUp() override { SerializeFixture::SetUp(); std::ofstream file(getCptPath()); assert(file.good()); file << R"cpt_file( [General] Test1=BARasdf Test2=bar [Junk] Test3=yo Test4=mama [Foo] Foo1=89 Foo2=384 [General] Test3=89 [Junk] Test4+=mia )cpt_file"; file.close(); cpt = std::make_unique(getDirName()); } void TearDown() override { std::remove((getCptPath()).c_str()); SerializeFixture::TearDown(); } }; /** * A fixture to handle checkpoint in and out variables, as well as the * testing of the temporary directory. */ class SerializableFixture : public SerializeFixture { public: std::unique_ptr cpt_in; std::unique_ptr cpt_out; using SerializeFixture::SerializeFixture; void SetUp() override { SerializeFixture::SetUp(); cpt_out = std::make_unique(getCptPath()); assert(cpt_out->good()); cpt_in = std::make_unique(getDirName()); } void TearDown() override { std::remove((getCptPath()).c_str()); SerializeFixture::TearDown(); } }; using SerializeFixtureDeathTest = SerializeFixture; using CheckpointInFixtureDeathTest = CheckpointInFixture; using SerializableFixtureDeathTest = SerializableFixture; /** Tests that when setting a checkpoint dir it always ends with a slash. */ TEST(CheckpointInTest, SetGetDirSlash) { ASSERT_EQ(CheckpointIn::setDir(""), "/"); ASSERT_EQ(CheckpointIn::dir(), "/"); ASSERT_EQ(CheckpointIn::setDir("/"), "/"); ASSERT_EQ(CheckpointIn::dir(), "/"); ASSERT_EQ(CheckpointIn::setDir("test_cpt_dir"), "test_cpt_dir/"); ASSERT_EQ(CheckpointIn::dir(), "test_cpt_dir/"); ASSERT_EQ(CheckpointIn::setDir("test_cpt_dir_2/"), "test_cpt_dir_2/"); ASSERT_EQ(CheckpointIn::dir(), "test_cpt_dir_2/"); } /** * Tests that when the dir name has a "%d" curTick is added. It may also * work with other formats, but it is not intended. */ TEST(CheckpointInTest, SetGetDirTick) { ASSERT_EQ(CheckpointIn::setDir("tick%d"), "tick0/"); ASSERT_EQ(CheckpointIn::dir(), "tick0/"); ASSERT_EQ(CheckpointIn::setDir("ti%dck"), "ti0ck/"); ASSERT_EQ(CheckpointIn::dir(), "ti0ck/"); } /** * Test constructor failure by requesting the creation of a checkpoint in * a non-existent dir. */ TEST_F(SerializeFixtureDeathTest, ConstructorFailure) { // Assign another cpt dir to make sure that it is changed when a new // CheckpointIn instance is created CheckpointIn::setDir("test"); // Make sure file does not exist, so that the constructor will fail std::ifstream file(getCptPath()); assert(!file.good()); ASSERT_ANY_THROW(CheckpointIn cpt(getDirName())); } /** * Test constructor success by requesting the creation of a checkpoint * in a specific valid dir. The static cpt dir is change on instantiation. */ TEST_F(SerializeFixture, ConstructorSuccess) { // Assign another cpt dir to make sure that it is changed when a new // CheckpointIn instance is created CheckpointIn::setDir("test"); // Create the file before creating the cpt to make sure it exists std::ofstream file(getCptPath()); assert(file.good()); file.close(); CheckpointIn cpt(getDirName()); // When a new CheckpointIn instance is created the static cpt dir changes EXPECT_EQ(CheckpointIn::dir(), getDirName()); } /** * Test that changing the static cpt dir does not change the name of the * cpt dir of previously created instances. */ TEST_F(CheckpointInFixtureDeathTest, GetCptDir) { ASSERT_ANY_THROW(CheckpointIn("/random_dir_name")); } /** Test finding sections. */ TEST_F(CheckpointInFixture, FindSections) { // Successful searches ASSERT_TRUE(cpt->sectionExists("General")); ASSERT_TRUE(cpt->sectionExists("Junk")); ASSERT_TRUE(cpt->sectionExists("Foo")); // Failed searches ASSERT_FALSE(cpt->sectionExists("Junk2")); ASSERT_FALSE(cpt->sectionExists("Test1")); } /** Test finding entries. */ TEST_F(CheckpointInFixture, FindEntries) { // Successful searches ASSERT_TRUE(cpt->entryExists("General", "Test2")); ASSERT_TRUE(cpt->entryExists("Junk", "Test3")); ASSERT_TRUE(cpt->entryExists("Junk", "Test4")); ASSERT_TRUE(cpt->entryExists("General", "Test1")); ASSERT_TRUE(cpt->entryExists("General", "Test3")); // Failed searches ASSERT_FALSE(cpt->entryExists("Junk2", "test3")); ASSERT_FALSE(cpt->entryExists("Junk", "test4")); } /** Test extracting the values of entries. */ TEST_F(CheckpointInFixture, ExtractEntries) { std::string value; // Successful searches ASSERT_TRUE(cpt->find("General", "Test2", value)); ASSERT_EQ(value, "bar"); ASSERT_TRUE(cpt->find("Junk", "Test3", value)); ASSERT_EQ(value, "yo"); ASSERT_TRUE(cpt->find("Junk", "Test4", value)); ASSERT_EQ(value, "mama mia"); ASSERT_TRUE(cpt->find("General", "Test1", value)); ASSERT_EQ(value, "BARasdf"); ASSERT_TRUE(cpt->find("General", "Test3", value)); ASSERT_EQ(value, "89"); // Failed searches ASSERT_FALSE(cpt->find("Junk2", "test3", value)); ASSERT_FALSE(cpt->find("Junk", "test4", value)); } /** * Test that paths are increased and decreased according to the scope that * its SCS was created in (using CheckpointIn). */ TEST_F(CheckpointInFixture, SCSCptInPathScoped) { Serializable::ScopedCheckpointSection scs(*(cpt.get()), "Section1"); ASSERT_EQ(Serializable::currentSection(), "Section1"); { Serializable::ScopedCheckpointSection scs_2(*(cpt.get()), "Section2"); ASSERT_EQ(Serializable::currentSection(), "Section1.Section2"); Serializable::ScopedCheckpointSection scs_3(*(cpt.get()), "Section3"); ASSERT_EQ(Serializable::currentSection(), "Section1.Section2.Section3"); } Serializable::ScopedCheckpointSection scs_4(*(cpt.get()), "Section4"); ASSERT_EQ(Serializable::currentSection(), "Section1.Section4"); } /** * Test that paths are increased and decreased according to the scope that * its SCS was created in (using CheckpointOut). */ TEST_F(SerializeFixture, SCSCptOutPathScoped) { std::ofstream cpt(getCptPath()); Serializable::ScopedCheckpointSection scs(cpt, "Section1"); ASSERT_EQ(Serializable::currentSection(), "Section1"); { Serializable::ScopedCheckpointSection scs_2(cpt, "Section2"); ASSERT_EQ(Serializable::currentSection(), "Section1.Section2"); Serializable::ScopedCheckpointSection scs_3(cpt, "Section3"); ASSERT_EQ(Serializable::currentSection(), "Section1.Section2.Section3"); } Serializable::ScopedCheckpointSection scs_4(cpt, "Section4"); ASSERT_EQ(Serializable::currentSection(), "Section1.Section4"); } /** * Make sure that a SCS that uses a CheckpointIn does not change the * checkpoint file's contents. */ TEST_F(SerializeFixture, SCSNoChangeCptIn) { { // Create empty cpt file std::ofstream os(getCptPath()); assert(os.good()); os.close(); } CheckpointIn cpt(getDirName()); std::ifstream is(getCptPath()); assert(is.good()); ASSERT_EQ(is.peek(), std::ifstream::traits_type::eof()); Serializable::ScopedCheckpointSection scs(cpt, "Section1"); ASSERT_EQ(is.peek(), std::ifstream::traits_type::eof()); ASSERT_EQ(Serializable::currentSection(), "Section1"); } /** @return The ostream as a std::string. */ std::string getString(std::istream &is) { auto buf = is.rdbuf(); std::ostringstream oss; oss << buf; return oss.str(); } /** * Flushes the checkpoint and reads its contents. * * @param cpt The checkpoint to be flushed. * @param filename The name of the file to be read. * @return The contents in the filename, as a string. */ std::string getContents(std::ofstream &cpt, std::string filename) { // The checkpoint must be flushed, otherwise the file may not be up- // to-date and the assertions below will fail cpt.flush(); std::ifstream is(filename); assert(is.good()); return std::string(std::istreambuf_iterator(is), std::istreambuf_iterator()); } /** * Make sure that a SCS that uses a CheckpointOut changes the checkpoint * file's contents (single section). */ TEST_F(SerializableFixture, SCSChangeCptOutSingle) { Serializable::ScopedCheckpointSection scs(*cpt_out, "Section1"); ASSERT_EQ(getContents(*cpt_out, getCptPath()), "\n[Section1]\n"); } /** * Make sure that a SCS that uses a CheckpointOut changes the checkpoint * file's contents (multiple sections). */ TEST_F(SerializableFixture, SCSChangeCptOutMultiple) { std::string expected = ""; Serializable::ScopedCheckpointSection scs(*cpt_out, "Section1"); expected += "\n[Section1]\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); { Serializable::ScopedCheckpointSection scs_2(*cpt_out, "Section2"); expected += "\n[Section1.Section2]\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); Serializable::ScopedCheckpointSection scs_3(*cpt_out, "Section3"); expected += "\n[Section1.Section2.Section3]\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); } Serializable::ScopedCheckpointSection scs_4(*cpt_out, "Section4"); expected += "\n[Section1.Section4]\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); } /** * Make sure that a SCS that uses a CheckpointOut changes the checkpoint * file's contents (large test). */ TEST_F(SerializableFixture, SCSChangeCptOutLarge) { std::string expected = ""; Serializable::ScopedCheckpointSection scs(*cpt_out, "Section1"); expected += "\n[Section1]\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); { Serializable::ScopedCheckpointSection scs_2(*cpt_out, "Section2"); expected += "\n[Section1.Section2]\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); { Serializable::ScopedCheckpointSection scs_3(*cpt_out, "Section3"); expected += "\n[Section1.Section2.Section3]\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); } Serializable::ScopedCheckpointSection scs_4(*cpt_out, "Section4"); expected += "\n[Section1.Section2.Section4]\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); } Serializable::ScopedCheckpointSection scs_5(*cpt_out, "Section5"); expected += "\n[Section1.Section5]\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); { Serializable::ScopedCheckpointSection scs_6(*cpt_out, "Section6"); expected += "\n[Section1.Section5.Section6]\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); Serializable::ScopedCheckpointSection scs_7(*cpt_out, "Section7"); expected += "\n[Section1.Section5.Section6.Section7]\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); } Serializable::ScopedCheckpointSection scs_8(*cpt_out, "Section8"); expected += "\n[Section1.Section5.Section8]\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); } /** Test failure to create dir. Try to create a dir in a non-existent dir. */ TEST(SerializableDeathTest, GenerateCptOutFail) { std::ofstream cpt; const std::string dir_name = SerializeFixture::generateTempDirName(); ASSERT_FALSE(dirExists(dir_name)); ASSERT_ANY_THROW(Serializable::generateCheckpointOut( dir_name + "/b/a/n/a/n/a/", cpt)); } /** Test successful CheckpointOut generation with non-existent dir. */ TEST_F(SerializeFixture, GenerateCptOut) { // The fixture will auto-create the dir. Remove it. assert(rmdir(getDirName().c_str()) == 0); std::ofstream cpt; Serializable::generateCheckpointOut(getDirName(), cpt); ASSERT_TRUE(dirExists(getDirName())); // Make sure the checkpoint was properly created. Using EXPECT to // force removing the directory at the end EXPECT_NE(getContents(cpt, getCptPath()).find( "## checkpoint generated: "), std::string::npos); } /** Test successful CheckpointOut generation with existing dir. */ TEST_F(SerializeFixture, GenerateCptOutExistent) { assert(dirExists(getDirName())); // Create the checkpoint and make sure it has been properly created by // deleting it and making sure the function was successful std::ofstream cpt; Serializable::generateCheckpointOut(getDirName(), cpt); EXPECT_TRUE(remove((getCptPath()).c_str()) == 0); } class SerializableType : public Serializable { private: mutable bool _serialized = false; bool _unserialized = false; public: SerializableType() : Serializable() {} void serialize(CheckpointOut &cp) const override { _serialized = true; } void unserialize(CheckpointIn &cp) override { _unserialized = true; } /** * Checks if serialize() has been called and then marks it as not called. * * @return True if serialize() has been called. */ bool checkAndResetSerialized() { const bool serialized = _serialized; _serialized = false; return serialized; } /** * Checks if unserialize() has been called and then marks it as not called. * * @return True if unserialize() has been called. */ bool checkAndResetUnserialized() { const bool unserialized = _unserialized; _unserialized = false; return unserialized; } }; /** * Test section serialization and unserialization for an object without * serializable contents. Since how (un)serialize() works is independent of * the Serializable class, we just make sure that the respective functions * are called when calling (un)serializeSection(). */ TEST_F(SerializableFixture, SectionSerializationSimple) { SerializableType serializable; // Serialization { serializable.serializeSection(*cpt_out, "Section1"); ASSERT_EQ(getContents(*cpt_out, getCptPath()), "\n[Section1]\n"); ASSERT_TRUE(serializable.checkAndResetSerialized()); ASSERT_FALSE(serializable.checkAndResetUnserialized()); serializable.serializeSection(*cpt_out, "Section2"); ASSERT_EQ(getContents(*cpt_out, getCptPath()), "\n[Section1]\n\n[Section2]\n"); ASSERT_TRUE(serializable.checkAndResetSerialized()); ASSERT_FALSE(serializable.checkAndResetUnserialized()); serializable.serializeSection(*cpt_out, "Section3"); ASSERT_EQ(getContents(*cpt_out, getCptPath()), "\n[Section1]\n\n[Section2]\n\n[Section3]\n"); ASSERT_TRUE(serializable.checkAndResetSerialized()); ASSERT_FALSE(serializable.checkAndResetUnserialized()); } // Unserialization. Since the object has no serializable contents, // just make sure it does not throw for existent and non-existent // sections { ASSERT_NO_THROW(serializable.unserializeSection(*cpt_in, "Section1")); ASSERT_FALSE(serializable.checkAndResetSerialized()); ASSERT_TRUE(serializable.checkAndResetUnserialized()); ASSERT_NO_THROW(serializable.unserializeSection(*cpt_in, "Section2")); ASSERT_FALSE(serializable.checkAndResetSerialized()); ASSERT_TRUE(serializable.checkAndResetUnserialized()); ASSERT_NO_THROW(serializable.unserializeSection(*cpt_in, "Section3")); ASSERT_FALSE(serializable.checkAndResetSerialized()); ASSERT_TRUE(serializable.checkAndResetUnserialized()); ASSERT_NO_THROW(serializable.unserializeSection(*cpt_in, "Section4")); ASSERT_FALSE(serializable.checkAndResetSerialized()); ASSERT_TRUE(serializable.checkAndResetUnserialized()); } } /** * Test that paramIn called on a param that does not exist triggers an error. */ TEST_F(SerializableFixtureDeathTest, ParamIn) { int unserialized_integer; Serializable::ScopedCheckpointSection scs(*cpt_in, "Section1"); ASSERT_ANY_THROW(paramIn(*cpt_in, "Param1", unserialized_integer)); } /** * Test serialization followed by unserialization, using ParamInImpl and * ParamOut. */ TEST_F(SerializableFixture, ParamOutIn) { const int integer = 5; const double real = 3.7; const bool boolean = true; const std::string str = "string test"; const char character = 'c'; // Serialization { Serializable::ScopedCheckpointSection scs(*cpt_out, "Section1"); std::string expected = "\n[Section1]\n"; paramOut(*cpt_out, "Param1", integer); expected += "Param1=5\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); paramOut(*cpt_out, "Param2", real); expected += "Param2=3.7\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); paramOut(*cpt_out, "Param3", boolean); expected += "Param3=true\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); paramOut(*cpt_out, "Param4", str); expected += "Param4=string test\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); paramOut(*cpt_out, "Param5", character); expected += "Param5=99\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); } // Unserialization { CheckpointIn cpt(getDirName()); int unserialized_integer; double unserialized_real; bool unserialized_boolean; std::string unserialized_str; char unserialized_character; Serializable::ScopedCheckpointSection scs(cpt, "Section1"); paramIn(cpt, "Param1", unserialized_integer); ASSERT_EQ(integer, unserialized_integer); paramIn(cpt, "Param2", unserialized_real); ASSERT_EQ(real, unserialized_real); paramIn(cpt, "Param3", unserialized_boolean); ASSERT_EQ(boolean, unserialized_boolean); paramIn(cpt, "Param4", unserialized_str); ASSERT_EQ(str, unserialized_str); paramIn(cpt, "Param5", unserialized_character); ASSERT_EQ(character, unserialized_character); } } /** * Test serialization followed by unserialization, using ParamInImpl and * ParamOut, when multiple sections exist. */ TEST_F(SerializableFixture, ParamOutInMultipleSections) { const int integer = 5; const double real = 3.7; const bool boolean = true; const std::string str = "string test"; const char character = 'c'; // Serialization { Serializable::ScopedCheckpointSection scs(*cpt_out, "Section1"); std::string expected = "\n[Section1]\n"; paramOut(*cpt_out, "Param1", integer); expected += "Param1=5\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); paramOut(*cpt_out, "Param2", real); expected += "Param2=3.7\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); { Serializable::ScopedCheckpointSection scs_2(*cpt_out, "Section2"); expected += "\n[Section1.Section2]\n"; paramOut(*cpt_out, "Param3", boolean); expected += "Param3=true\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); } // Possibly unexpected behavior: Since scs_2 has gone out of scope // the user may expect that we'd go back to Section1; however, this // is not the case, and Param4 is added to Section1.Section2 paramOut(*cpt_out, "Param4", str); expected += "Param4=string test\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); { Serializable::ScopedCheckpointSection scs_3(*cpt_out, "Section3"); expected += "\n[Section1.Section3]\n"; Serializable::ScopedCheckpointSection scs_4(*cpt_out, "Section4"); expected += "\n[Section1.Section3.Section4]\n"; paramOut(*cpt_out, "Param5", character); expected += "Param5=99\n"; ASSERT_EQ(getContents(*cpt_out, getCptPath()), expected); } } // Unserialization { CheckpointIn cpt(getDirName()); int unserialized_integer; double unserialized_real; bool unserialized_boolean; std::string unserialized_str; char unserialized_character; Serializable::ScopedCheckpointSection scs(cpt, "Section1"); paramIn(cpt, "Param1", unserialized_integer); ASSERT_EQ(integer, unserialized_integer); paramIn(cpt, "Param2", unserialized_real); ASSERT_EQ(real, unserialized_real); { Serializable::ScopedCheckpointSection scs_2(cpt, "Section2"); paramIn(cpt, "Param3", unserialized_boolean); ASSERT_EQ(boolean, unserialized_boolean); // Due to the reason mentioned above on serialization, this param // must be extracted within the scope of Section 2 paramIn(cpt, "Param4", unserialized_str); ASSERT_EQ(str, unserialized_str); } { Serializable::ScopedCheckpointSection scs_3(cpt, "Section3"); Serializable::ScopedCheckpointSection scs_4(cpt, "Section4"); paramIn(cpt, "Param5", unserialized_character); ASSERT_EQ(character, unserialized_character); } } } /** Test optional parameters. */ TEST_F(SerializeFixture, OptParamOutIn) { const double real = 3.7; const std::string str = "string test"; // Serialization { std::ofstream cpt(getCptPath()); Serializable::ScopedCheckpointSection scs(cpt, "Section1"); paramOut(cpt, "Param2", real); paramOut(cpt, "Param4", str); } // Unserialization { CheckpointIn cpt(getDirName()); int unserialized_integer; double unserialized_real; bool unserialized_boolean; std::string unserialized_str; Serializable::ScopedCheckpointSection scs(cpt, "Section1"); // Optional without warning gtestLogOutput.str(""); ASSERT_FALSE(optParamIn(cpt, "Param1", unserialized_integer, false)); ASSERT_EQ(gtestLogOutput.str(), ""); gtestLogOutput.str(""); ASSERT_TRUE(optParamIn(cpt, "Param2", unserialized_real, false)); ASSERT_EQ(real, unserialized_real); ASSERT_EQ(gtestLogOutput.str(), ""); // Optional with default request for warning gtestLogOutput.str(""); ASSERT_FALSE(optParamIn(cpt, "Param3", unserialized_boolean)); ASSERT_THAT(gtestLogOutput.str(), ::testing::HasSubstr("warn: optional parameter Section1:Param3 " "not present\n")); gtestLogOutput.str(""); ASSERT_TRUE(optParamIn(cpt, "Param4", unserialized_str)); ASSERT_EQ(str, unserialized_str); ASSERT_EQ(gtestLogOutput.str(), ""); // Explicit request for warning gtestLogOutput.str(""); ASSERT_FALSE(optParamIn(cpt, "Param5", unserialized_boolean, true)); ASSERT_THAT(gtestLogOutput.str(), ::testing::HasSubstr("warn: optional parameter Section1:Param5 " "not present\n")); } } /** Check for death when there is no section and paramIn is requested. */ TEST_F(SerializableFixtureDeathTest, NoSectionParamIn) { #ifndef NDEBUG GTEST_SKIP() << "Skipping as assertions are " "stripped out of fast builds"; #endif std::ofstream of(getCptPath()); CheckpointIn cpt(getDirName()); int unserialized_integer; ASSERT_DEATH(paramIn(cpt, "Param1", unserialized_integer), ""); } /** Test arrayParamOut and arrayParamIn. */ TEST_F(SerializeFixture, ArrayParamOutIn) { const int integer[] = {5, 10, 15}; std::array real = {0.1, 1.345, 892.72, 1e+10}; std::list boolean = {true, false}; std::vector str = {"a", "string", "test"}; std::set uint64 = {12751928501, 13, 111111}; std::deque uint8 = {17, 42, 255}; // Serialization { std::ofstream cpt(getCptPath()); Serializable::ScopedCheckpointSection scs(cpt, "Section1"); std::string expected = "\n[Section1]\n"; arrayParamOut(cpt, "Param1", integer); expected += "Param1=5 10 15\n"; ASSERT_EQ(getContents(cpt, getCptPath()), expected); arrayParamOut(cpt, "Param2", real); expected += "Param2=0.1 1.345 892.72 1e+10\n"; ASSERT_EQ(getContents(cpt, getCptPath()), expected); arrayParamOut(cpt, "Param3", boolean); expected += "Param3=true false\n"; ASSERT_EQ(getContents(cpt, getCptPath()), expected); arrayParamOut(cpt, "Param4", str); expected += "Param4=a string test\n"; ASSERT_EQ(getContents(cpt, getCptPath()), expected); arrayParamOut(cpt, "Param5", uint64); expected += "Param5=13 111111 12751928501\n"; ASSERT_EQ(getContents(cpt, getCptPath()), expected); arrayParamOut(cpt, "Param6", uint8); expected += "Param6=17 42 255\n"; ASSERT_EQ(getContents(cpt, getCptPath()), expected); } // Unserialization { CheckpointIn cpt(getDirName()); int unserialized_integer[3]; std::array unserialized_real; std::list unserialized_boolean; std::vector unserialized_str; std::set unserialized_uint64; std::deque unserialized_uint8; Serializable::ScopedCheckpointSection scs(cpt, "Section1"); arrayParamIn(cpt, "Param1", unserialized_integer, 3); ASSERT_THAT(unserialized_integer, testing::ElementsAre(5, 10, 15)); arrayParamIn(cpt, "Param2", unserialized_real.data(), unserialized_real.size()); ASSERT_EQ(real, unserialized_real); arrayParamIn(cpt, "Param3", unserialized_boolean); ASSERT_EQ(boolean, unserialized_boolean); arrayParamIn(cpt, "Param4", unserialized_str); ASSERT_EQ(str, unserialized_str); arrayParamIn(cpt, "Param5", unserialized_uint64); ASSERT_EQ(uint64, unserialized_uint64); arrayParamIn(cpt, "Param6", unserialized_uint8); ASSERT_EQ(uint8, unserialized_uint8); } } /** * Test arrayParamOut and arrayParamIn for strings with spaces. * @todo This is broken because spaces are delimiters between array * entries; so, strings containing spaces are seen as multiple entries */ TEST_F(SerializeFixture, DISABLED_ArrayParamOutInSpacedStrings) { std::vector str = {"a string test", "for", "array param out"}; // Serialization { std::ofstream cpt(getCptPath()); Serializable::ScopedCheckpointSection scs(cpt, "Section1"); std::string expected = "\n[Section1]\n"; arrayParamOut(cpt, "Param1", str); expected += "Param1=string test for array param out\n"; ASSERT_EQ(getContents(cpt, getCptPath()), expected); } // Unserialization { CheckpointIn cpt(getDirName()); std::vector unserialized_str; Serializable::ScopedCheckpointSection scs(cpt, "Section1"); arrayParamIn(cpt, "Param1", unserialized_str); ASSERT_EQ(str, unserialized_str); } } /** * Test that using arrayParamIn with sizes smaller than the container's * throws an exception. */ TEST_F(SerializeFixtureDeathTest, ArrayParamOutInSmaller) { // Serialization { const int integer[] = {5, 10, 15}; std::ofstream cpt(getCptPath()); Serializable::ScopedCheckpointSection scs(cpt, "Section1"); std::string expected = "\n[Section1]\n"; arrayParamOut(cpt, "Param1", integer); expected += "Param1=5 10 15\n"; ASSERT_EQ(getContents(cpt, getCptPath()), expected); } // Unserialization { CheckpointIn cpt(getDirName()); int unserialized_integer[1]; Serializable::ScopedCheckpointSection scs(cpt, "Section1"); ASSERT_ANY_THROW(arrayParamIn(cpt, "Param1", unserialized_integer, 2)); } } /** Test mappingParamOut and mappingParamIn with all keys. */ TEST_F(SerializeFixture, MappingParamOutIn) { const int integers[] = {10, 32, 100}; std::array reals = {0.1, 1.345, 892.72, 1e+10}; const char* const names_ints[] = {"ten", "thirty-two", "one hundred"}; const char* const names_reals[] = {"first", "second", "third", "fourth"}; // Serialization { std::ofstream cpt(getCptPath()); Serializable::ScopedCheckpointSection scs(cpt, "Section1"); std::string expected = "\n[Section1]\n"; mappingParamOut(cpt, "Integers", names_ints, integers, 3); expected += "\n[Section1.Integers]\nten=10\nthirty-two=32\n" "one hundred=100\n"; ASSERT_EQ(getContents(cpt, getCptPath()), expected); mappingParamOut(cpt, "Reals", names_reals, reals.data(), reals.size()); expected += "\n[Section1.Reals]\nfirst=0.1\nsecond=1.345\n" "third=892.72\nfourth=1e+10\n"; ASSERT_EQ(getContents(cpt, getCptPath()), expected); } // Unserialization { CheckpointIn cpt(getDirName()); int unserialized_integers[3]; std::array unserialized_reals; mappingParamIn(cpt, "Section1.Integers", names_ints, unserialized_integers, 3); ASSERT_THAT(unserialized_integers, testing::ElementsAre(10, 32, 100)); mappingParamIn(cpt, "Section1.Reals", names_reals, unserialized_reals.data(), unserialized_reals.size()); ASSERT_EQ(unserialized_reals, reals); } } /** Test that missing keys are ignored on mappingParamIn. */ TEST_F(SerializeFixture, MappingParamOutInMissing) { const int integers[] = {10, 32, 100}; std::array reals = {0.1, 1.345, 892.72, 1e+10}; // Serialization { const char* const names_ints[] = {"ten", "thirty-two", "one hundred"}; const char* const names_reals[] = {"first", "second", "third", "fourth"}; std::ofstream cpt(getCptPath()); Serializable::ScopedCheckpointSection scs(cpt, "Section1"); std::string expected = "\n[Section1]\n"; mappingParamOut(cpt, "Integers", names_ints, integers, 3); expected += "\n[Section1.Integers]\nten=10\nthirty-two=32\n" "one hundred=100\n"; ASSERT_EQ(getContents(cpt, getCptPath()), expected); mappingParamOut(cpt, "Reals", names_reals, reals.data(), reals.size()); expected += "\n[Section1.Reals]\nfirst=0.1\nsecond=1.345\n" "third=892.72\nfourth=1e+10\n"; ASSERT_EQ(getContents(cpt, getCptPath()), expected); } // Unserialization { const char* const names_ints[] = {"one hundred"}; const char* const names_reals[] = {"first", "third"}; std::array expected_reals = {0.1, 892.72}; CheckpointIn cpt(getDirName()); std::string err; int unserialized_integers[1]; std::array unserialized_reals; gtestLogOutput.str(""); mappingParamIn(cpt, "Section1.Integers", names_ints, unserialized_integers, 1); ASSERT_THAT(unserialized_integers, testing::ElementsAre(100)); err = gtestLogOutput.str(); ASSERT_THAT(err, ::testing::HasSubstr("warn: unknown entry found in " "checkpoint: Section1.Integers thirty-two 32\n")); ASSERT_THAT(err, ::testing::HasSubstr("warn: unknown entry found in " "checkpoint: Section1.Integers ten 10\n")); gtestLogOutput.str(""); mappingParamIn(cpt, "Section1.Reals", names_reals, unserialized_reals.data(), unserialized_reals.size()); ASSERT_EQ(unserialized_reals, expected_reals); err = gtestLogOutput.str(); ASSERT_THAT(err, ::testing::HasSubstr("warn: unknown entry found in " "checkpoint: Section1.Reals fourth 1e+10\n")); ASSERT_THAT(err, ::testing::HasSubstr("warn: unknown entry found in " "checkpoint: Section1.Reals second 1.345\n")); } } /** * Test serialization followed by unserialization, using SERIALIZE_SCALAR and * UNSERIALIZE_SCALAR. */ TEST_F(SerializeFixture, SerializeScalar) { const int expected_integer = 5; const double expected_real = 3.7; const bool expected_boolean = true; const std::string expected_str = "string test"; // Serialization { const int integer = expected_integer; const double real = expected_real; const bool boolean = expected_boolean; const std::string str = expected_str; std::ofstream cp(getCptPath()); Serializable::ScopedCheckpointSection scs(cp, "Section1"); std::string expected = "\n[Section1]\n"; SERIALIZE_SCALAR(integer); expected += "integer=5\n"; ASSERT_EQ(getContents(cp, getCptPath()), expected); SERIALIZE_SCALAR(real); expected += "real=3.7\n"; ASSERT_EQ(getContents(cp, getCptPath()), expected); SERIALIZE_SCALAR(boolean); expected += "boolean=true\n"; ASSERT_EQ(getContents(cp, getCptPath()), expected); SERIALIZE_SCALAR(str); expected += "str=string test\n"; ASSERT_EQ(getContents(cp, getCptPath()), expected); } // Unserialization { CheckpointIn cp(getDirName()); int integer; double real; bool boolean; std::string str; Serializable::ScopedCheckpointSection scs(cp, "Section1"); UNSERIALIZE_SCALAR(integer); ASSERT_EQ(integer, expected_integer); UNSERIALIZE_SCALAR(real); ASSERT_EQ(real, expected_real); UNSERIALIZE_SCALAR(boolean); ASSERT_EQ(boolean, expected_boolean); UNSERIALIZE_SCALAR(str); ASSERT_EQ(str, expected_str); } } /** Test optional parameters with UNSERIALIZE_OPT_SCALAR. */ TEST_F(SerializeFixture, UnserializeOptScalar) { const double expected_real = 3.7; const std::string expected_str = "string test"; // Serialization { const double real = expected_real; const std::string str = expected_str; std::ofstream cp(getCptPath()); Serializable::ScopedCheckpointSection scs(cp, "Section1"); SERIALIZE_SCALAR(real); SERIALIZE_SCALAR(str); } // Unserialization { CheckpointIn cp(getDirName()); int integer; double real; bool boolean; std::string str; Serializable::ScopedCheckpointSection scs(cp, "Section1"); // Optional without warning ASSERT_FALSE(UNSERIALIZE_OPT_SCALAR(integer)); ASSERT_TRUE(UNSERIALIZE_OPT_SCALAR(real)); ASSERT_EQ(real, expected_real); // Optional with default request for warning ASSERT_FALSE(UNSERIALIZE_OPT_SCALAR(boolean)); ASSERT_TRUE(UNSERIALIZE_OPT_SCALAR(str)); ASSERT_EQ(str, expected_str); } } /** * Test serialization followed by unserialization, using SERIALIZE_ENUM and * UNSERIALIZE_ENUM. */ TEST_F(SerializeFixture, SerializeEnum) { enum Number { ZERO, TEN=10, THIRTY_TWO=32 }; const Number expected_val = ZERO; const Number expected_val_2 = THIRTY_TWO; // Serialization { const Number zero = expected_val; const Number thirty_two = expected_val_2; std::ofstream cp(getCptPath()); Serializable::ScopedCheckpointSection scs(cp, "Section1"); std::string expected = "\n[Section1]\n"; SERIALIZE_ENUM(zero); expected += "zero=0\n"; ASSERT_EQ(getContents(cp, getCptPath()), expected); SERIALIZE_ENUM(thirty_two); expected += "thirty_two=32\n"; ASSERT_EQ(getContents(cp, getCptPath()), expected); } // Unserialization { CheckpointIn cp(getDirName()); Number zero; Number thirty_two; Serializable::ScopedCheckpointSection scs(cp, "Section1"); UNSERIALIZE_ENUM(zero); ASSERT_EQ(zero, expected_val); UNSERIALIZE_ENUM(thirty_two); ASSERT_EQ(thirty_two, expected_val_2); } } /** Test SERIALIZE_ARRAY and UNSERIALIZE_ARRAY. */ TEST_F(SerializeFixture, SerializeArray) { std::array expected_real = {0.1, 1.345, 892.72, 1e+10}; // Serialization { const int integer[] = {5, 10, 15}; const double *real = expected_real.data(); std::ofstream cp(getCptPath()); Serializable::ScopedCheckpointSection scs(cp, "Section1"); std::string expected = "\n[Section1]\n"; SERIALIZE_ARRAY(integer, 3); expected += "integer=5 10 15\n"; ASSERT_EQ(getContents(cp, getCptPath()), expected); SERIALIZE_ARRAY(real, expected_real.size()); expected += "real=0.1 1.345 892.72 1e+10\n"; ASSERT_EQ(getContents(cp, getCptPath()), expected); } // Unserialization { CheckpointIn cp(getDirName()); int integer[3]; std::array real_array; double *real = real_array.data(); Serializable::ScopedCheckpointSection scs(cp, "Section1"); UNSERIALIZE_ARRAY(integer, 3); ASSERT_THAT(integer, testing::ElementsAre(5, 10, 15)); UNSERIALIZE_ARRAY(real, expected_real.size()); ASSERT_EQ(real_array, expected_real); } } /** Test SERIALIZE_CONTAINER and UNSERIALIZE_CONTAINER. */ TEST_F(SerializeFixture, SerializeContainer) { std::list expected_boolean = {true, false}; std::vector expected_str = {"a", "string", "test"}; std::set expected_uint64 = {12751928501, 13, 111111}; std::deque expected_uint8 = {17, 42, 255}; // Serialization { const std::list boolean = expected_boolean; const std::vector str = expected_str; const std::set uint64 = expected_uint64; const std::deque uint8 = expected_uint8; std::ofstream cp(getCptPath()); Serializable::ScopedCheckpointSection scs(cp, "Section1"); std::string expected = "\n[Section1]\n"; SERIALIZE_CONTAINER(boolean); expected += "boolean=true false\n"; ASSERT_EQ(getContents(cp, getCptPath()), expected); SERIALIZE_CONTAINER(str); expected += "str=a string test\n"; ASSERT_EQ(getContents(cp, getCptPath()), expected); SERIALIZE_CONTAINER(uint64); expected += "uint64=13 111111 12751928501\n"; ASSERT_EQ(getContents(cp, getCptPath()), expected); SERIALIZE_CONTAINER(uint8); expected += "uint8=17 42 255\n"; ASSERT_EQ(getContents(cp, getCptPath()), expected); } // Unserialization { CheckpointIn cp(getDirName()); std::list boolean; std::vector str; std::set uint64; std::deque uint8; Serializable::ScopedCheckpointSection scs(cp, "Section1"); UNSERIALIZE_CONTAINER(boolean); ASSERT_EQ(boolean, expected_boolean); UNSERIALIZE_CONTAINER(str); ASSERT_EQ(str, expected_str); UNSERIALIZE_CONTAINER(uint64); ASSERT_EQ(uint64, expected_uint64); UNSERIALIZE_CONTAINER(uint8); ASSERT_EQ(uint8, expected_uint8); } } /** Test SERIALIZE_MAPPING and UNSERIALIZE_MAPPING with all keys. */ TEST_F(SerializeFixture, SerializeMapping) { const int expected_integers[] = {10, 32, 100}; std::array expected_reals = {0.1, 1.345, 892.72, 1e+10}; const char* const names_ints[] = {"ten", "thirty-two", "one hundred"}; const char* const names_reals[] = {"first", "second", "third", "fourth"}; // Serialization { const int *integers = expected_integers; const double *reals = expected_reals.data(); std::ofstream cp(getCptPath()); Serializable::ScopedCheckpointSection scs(cp, "Section1"); std::string expected = "\n[Section1]\n"; SERIALIZE_MAPPING(integers, names_ints, 3); expected += "\n[Section1.integers]\nten=10\nthirty-two=32\n" "one hundred=100\n"; ASSERT_EQ(getContents(cp, getCptPath()), expected); SERIALIZE_MAPPING(reals, names_reals, expected_reals.size()); expected += "\n[Section1.reals]\nfirst=0.1\nsecond=1.345\n" "third=892.72\nfourth=1e+10\n"; ASSERT_EQ(getContents(cp, getCptPath()), expected); } // Unserialization { CheckpointIn cp(getDirName()); int integers[3]; double reals[4]; Serializable::ScopedCheckpointSection scs(cp, "Section1"); UNSERIALIZE_MAPPING(integers, names_ints, 3); ASSERT_THAT(integers, testing::ElementsAre(10, 32, 100)); UNSERIALIZE_MAPPING(reals, names_reals, expected_reals.size()); ASSERT_THAT(reals, testing::ElementsAre(0.1, 1.345, 892.72, 1e+10)); } }