From 55d6c431f2492e3a423fe03cc48260f65f82c5cf Mon Sep 17 00:00:00 2001 From: melex750 Date: Fri, 25 Jun 2021 18:50:15 -0400 Subject: [PATCH] Fix a bug in WriteStream and ReadStream Added a unit test to confirm reliability of I/O functions used by SaveState and RestoreState. --- src/CBot/CBotFileUtils.cpp | 8 +- test/unit/CBot/CBot_test.cpp | 195 +++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+), 2 deletions(-) diff --git a/src/CBot/CBotFileUtils.cpp b/src/CBot/CBotFileUtils.cpp index 7a614558..070e8cca 100644 --- a/src/CBot/CBotFileUtils.cpp +++ b/src/CBot/CBotFileUtils.cpp @@ -342,7 +342,11 @@ bool WriteStream(std::ostream &ostr, std::istream& istr) if (!WriteLong(ostr, size)) return false; if (!istr.seekg(0, istr.beg)) return false; - if (!(ostr << istr.rdbuf())) return false; + while (size > 0) + { + size -= 1; + if (!ostr.put(istr.get())) return false; + } return true; } @@ -355,7 +359,7 @@ bool ReadStream(std::istream& istr, std::ostream &ostr) while (length-- > 0) { - if (!(ostr << istr.get())) return false; + if (!ostr.put(istr.get())) return false; } return true; } diff --git a/test/unit/CBot/CBot_test.cpp b/test/unit/CBot/CBot_test.cpp index 4f4f37c7..cd7dda47 100644 --- a/test/unit/CBot/CBot_test.cpp +++ b/test/unit/CBot/CBot_test.cpp @@ -20,6 +20,7 @@ #include "CBot/CBot.h" #include +#include #include extern bool g_cbotTestSaveState; @@ -319,6 +320,200 @@ protected: } }; +TEST_F(CBotUT, TestSaveStateIOFunctions) +{ + std::stringstream sstr(""); + std::string teststring; + + for (char c = std::numeric_limits::min() ;; ++c) + { + teststring.push_back(c); + if ( c == std::numeric_limits::max() ) break; + } + + auto CallWriteFunctions = [&sstr, &teststring]() -> bool + { + if (!WriteWord(sstr, static_cast(0))) return false; + if (!WriteWord(sstr, std::numeric_limits::max() / 2)) return false; + if (!WriteWord(sstr, std::numeric_limits::max())) return false; + + if (!WriteByte(sstr, std::numeric_limits::min())) return false; + if (!WriteByte(sstr, std::numeric_limits::max())) return false; + + if (!WriteShort(sstr, std::numeric_limits::min())) return false; + if (!WriteShort(sstr, std::numeric_limits::min() / 2)) return false; + if (!WriteShort(sstr, -1)) return false; + if (!WriteShort(sstr, 0)) return false; + if (!WriteShort(sstr, std::numeric_limits::max() / 2)) return false; + if (!WriteShort(sstr, std::numeric_limits::max())) return false; + + if (!WriteUInt32(sstr, static_cast(0))) return false; + if (!WriteUInt32(sstr, std::numeric_limits::max() / 2)) return false; + if (!WriteUInt32(sstr, std::numeric_limits::max())) return false; + + if (!WriteInt(sstr, std::numeric_limits::min())) return false; + if (!WriteInt(sstr, std::numeric_limits::min() / 2)) return false; + if (!WriteInt(sstr, -1)) return false; + if (!WriteInt(sstr, 0)) return false; + if (!WriteInt(sstr, std::numeric_limits::max() / 2)) return false; + if (!WriteInt(sstr, std::numeric_limits::max())) return false; + + if (!WriteLong(sstr, std::numeric_limits::min())) return false; + if (!WriteLong(sstr, std::numeric_limits::min() / 2L)) return false; + if (!WriteLong(sstr, -1L)) return false; + if (!WriteLong(sstr, 0L)) return false; + if (!WriteLong(sstr, std::numeric_limits::max() / 2L)) return false; + if (!WriteLong(sstr, std::numeric_limits::max())) return false; + + // test with padding bytes (not currently used anywhere) + if (!WriteLong(sstr, 1234567890L, 10)) return false; + + if (!WriteFloat(sstr, std::numeric_limits::min())) return false; + if (!WriteFloat(sstr, 0.0f)) return false; + if (!WriteFloat(sstr, std::numeric_limits::max())) return false; + + if (!WriteDouble(sstr, std::numeric_limits::min())) return false; + if (!WriteDouble(sstr, 0.0)) return false; + if (!WriteDouble(sstr, std::numeric_limits::max())) return false; + + if (!WriteString(sstr, "")) return false; + if (!WriteString(sstr, teststring)) return false; + return true; + }; + + if ( !CallWriteFunctions() ) + { + ADD_FAILURE() << "failed in CallWriteFunctions()" << std::endl; + return; + } + + std::stringstream savestream(""); + + if ( !WriteStream(savestream, sstr) ) + { + ADD_FAILURE() << "CBot::WriteStream() failed" << std::endl; + return; + } + + std::stringstream newstream(""); + + if ( !ReadStream(savestream, newstream) ) + { + ADD_FAILURE() << "CBot:ReadStream() failed" << std::endl; + return; + } + + auto CallReadFunctions = [&teststring, &newstream]() -> bool + { + unsigned short w = 1; + if (!ReadWord(newstream, w)) return false; + if (w != static_cast(0)) return false; + if (!ReadWord(newstream, w)) return false; + if (w != std::numeric_limits::max() / 2) return false; + + if (!ReadWord(newstream, w)) return false; + if (w != std::numeric_limits::max()) return false; + + char c = 1; + if (!ReadByte(newstream, c)) return false; + if (c != std::numeric_limits::min()) return false; + if (!ReadByte(newstream, c)) return false; + if (c != std::numeric_limits::max()) return false; + + short s = 1; + if (!ReadShort(newstream, s)) return false; + if (s != std::numeric_limits::min()) return false; + if (!ReadShort(newstream, s)) return false; + if (s != std::numeric_limits::min() / 2) return false; + + if (!ReadShort(newstream, s)) return false; + if (s != -1) return false; + if (!ReadShort(newstream, s)) return false; + if (s != 0) return false; + + if (!ReadShort(newstream, s)) return false; + if (s != std::numeric_limits::max() / 2) return false; + if (!ReadShort(newstream, s)) return false; + if (s != std::numeric_limits::max()) return false; + + uint32_t u = 1; + if (!ReadUInt32(newstream, u)) return false; + if (u != static_cast(0)) return false; + if (!ReadUInt32(newstream, u)) return false; + if (u != std::numeric_limits::max() / 2) return false; + + if (!ReadUInt32(newstream, u)) return false; + if (u != std::numeric_limits::max()) return false; + + int i = 1; + if (!ReadInt(newstream, i)) return false; + if (i != std::numeric_limits::min()) return false; + if (!ReadInt(newstream, i)) return false; + if (i != std::numeric_limits::min() / 2) return false; + + if (!ReadInt(newstream, i)) return false; + if (i != -1) return false; + if (!ReadInt(newstream, i)) return false; + if (i != 0) return false; + + if (!ReadInt(newstream, i)) return false; + if (i != std::numeric_limits::max() / 2) return false; + if (!ReadInt(newstream, i)) return false; + if (i != std::numeric_limits::max()) return false; + + long l = 1L; + if (!ReadLong(newstream, l)) return false; + if (l != std::numeric_limits::min()) return false; + if (!ReadLong(newstream, l)) return false; + if (l != std::numeric_limits::min() / 2L) return false; + + if (!ReadLong(newstream, l)) return false; + if (l != -1L) return false; + if (!ReadLong(newstream, l)) return false; + if (l != 0L) return false; + + if (!ReadLong(newstream, l)) return false; + if (l != std::numeric_limits::max() / 2L) return false; + if (!ReadLong(newstream, l)) return false; + if (l != std::numeric_limits::max()) return false; + + if (!ReadLong(newstream, l)) return false; + if (l != 1234567890L) return false; + + float f = 1.0f; + if (!ReadFloat(newstream, f)) return false; + if (f != std::numeric_limits::min()) return false; + if (!ReadFloat(newstream, f)) return false; + if (f != 0.0f) return false; + + if (!ReadFloat(newstream, f)) return false; + if (f != std::numeric_limits::max()) return false; + + double d = 1.0; + if (!ReadDouble(newstream, d)) return false; + if (d != std::numeric_limits::min()) return false; + if (!ReadDouble(newstream, d)) return false; + if (d != 0.0) return false; + + if (!ReadDouble(newstream, d)) return false; + if (d != std::numeric_limits::max()) return false; + + std::string newstring = "should be empty string after next read"; + if (!ReadString(newstream, newstring)) return false; + if (newstring != "") return false; + + if (!ReadString(newstream, newstring)) return false; + if (newstring != teststring) return false; + return true; + }; + + if ( !CallReadFunctions() ) + { + ADD_FAILURE() << "failed in CallReadFunctions()" << std::endl; + return; + } +} + TEST_F(CBotUT, EmptyTest) { ExecuteTest(