diff --git a/simgear/io/iostreams/CMakeLists.txt b/simgear/io/iostreams/CMakeLists.txt index 37be39c7..5ad22434 100644 --- a/simgear/io/iostreams/CMakeLists.txt +++ b/simgear/io/iostreams/CMakeLists.txt @@ -4,6 +4,7 @@ set(HEADERS sgstream.hxx gzfstream.hxx gzcontainerfile.hxx + CharArrayStream.hxx zlibstream.hxx ) @@ -11,6 +12,7 @@ set(SOURCES sgstream.cxx gzfstream.cxx gzcontainerfile.cxx + CharArrayStream.cxx zlibstream.cxx ) @@ -22,6 +24,10 @@ if(ENABLE_TESTS) target_link_libraries(test_streams ${TEST_LIBS}) add_test(streams ${EXECUTABLE_OUTPUT_PATH}/test_streams) + add_executable(test_CharArrayStream CharArrayStream_test.cxx) + target_link_libraries(test_CharArrayStream ${TEST_LIBS}) + add_test(CharArrayStream ${EXECUTABLE_OUTPUT_PATH}/test_CharArrayStream) + add_executable(test_zlibstream zlibstream_test.cxx) target_link_libraries(test_zlibstream ${TEST_LIBS}) add_test(zlibstream ${EXECUTABLE_OUTPUT_PATH}/test_zlibstream) diff --git a/simgear/io/iostreams/CharArrayStream.cxx b/simgear/io/iostreams/CharArrayStream.cxx new file mode 100644 index 00000000..1b0bfe2e --- /dev/null +++ b/simgear/io/iostreams/CharArrayStream.cxx @@ -0,0 +1,291 @@ +// -*- coding: utf-8 -*- +// +// CharArrayStream.cxx --- IOStreams classes for reading from, and writing to +// char arrays +// +// Copyright (C) 2017 Florent Rougon +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library 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 +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301 USA. + +#include + +#include // std::streamsize +#include // std::istream and std::iostream +#include // std::ostream +#include // std::make_unsigned() +#include // std::size_t, std::ptrdiff_t +#include + +#include "CharArrayStream.hxx" + +using traits = std::char_traits; + + +namespace simgear +{ + +// *************************************************************************** +// * CharArrayStreambuf class * +// *************************************************************************** + +CharArrayStreambuf::CharArrayStreambuf(char* buf, std::size_t bufSize) + : _buf(buf), + _bufSize(bufSize) +{ + setg(_buf, _buf, _buf + _bufSize); + setp(_buf, _buf + _bufSize); +} + +char* CharArrayStreambuf::data() const +{ + return _buf; +} + +std::size_t CharArrayStreambuf::size() const +{ + return _bufSize; +} + +int CharArrayStreambuf::underflow() +{ + return (gptr() == egptr()) ? traits::eof() : traits::to_int_type(*gptr()); +} + +int CharArrayStreambuf::overflow(int c) +{ + // cf. §27.7.1, footnote 309 of the C++11 standard + if (traits::eq_int_type(c, traits::eof())) { + return traits::not_eof(c); + } else { + // This class never writes beyond the end of the array (_buf + _bufSize) + return traits::eof(); + } +} + +std::streamsize CharArrayStreambuf::xsgetn(char* dest, std::streamsize n) +{ + assert(n >= 0); + std::ptrdiff_t avail = egptr() - gptr(); + // Compute min(avail, n). The cast is safe, because in its branch, one has + // 0 <= n < avail, which is of type std::ptrdiff_t. + std::ptrdiff_t nbChars = ( (n >= avail) ? + avail : static_cast(n) ); + std::copy(gptr(), gptr() + nbChars, dest); + // eback() == _buf and egptr() == _buf + _bufSize + // I don't use gbump(), because it takes an int... + setg(eback(), gptr() + nbChars, egptr()); + + // Cast safe because 0 <= nbChars <= n, which is of type std::streamsize + return static_cast(nbChars); // number of chars copied +} + +std::streamsize CharArrayStreambuf::xsputn(const char* s, std::streamsize n) +{ + assert(n >= 0); + std::ptrdiff_t availSpace = epptr() - pptr(); + // Compute min(availSpace, n). The cast is safe, because in its branch, one + // has 0 <= n < availSpace, which is of type std::ptrdiff_t. + std::ptrdiff_t nbChars = ( (n >= availSpace) ? + availSpace : static_cast(n) ); + std::copy(s, s + nbChars, pptr()); + // epptr() == _buf + _bufSize + // I don't use pbump(), because it takes an int... + setp(pptr() + nbChars, epptr()); + + // Cast safe because 0 <= nbChars <= n, which is of type std::streamsize + return static_cast(nbChars); // number of chars copied +} + +std::streamsize CharArrayStreambuf::showmanyc() +{ + // It is certain that underflow() will return EOF if gptr() == egptr(). + return -1; +} + +std::streampos CharArrayStreambuf::seekoff(std::streamoff off, + std::ios_base::seekdir way, + std::ios_base::openmode which) +{ + bool positionInputSeq = false; + bool positionOutputSeq = false; + char* ptr = nullptr; + + // cf. §27.8.2.4 of the C++11 standard + if ((which & std::ios_base::in) == std::ios_base::in) { + positionInputSeq = true; + ptr = gptr(); + } + + if ((which & std::ios_base::out) == std::ios_base::out) { + positionOutputSeq = true; + ptr = pptr(); + } + + if ((!positionInputSeq && !positionOutputSeq) || + (positionInputSeq && positionOutputSeq && + way != std::ios_base::beg && way != std::ios_base::end)) { + return std::streampos(std::streamoff(-1)); + } + + // If we reached this point and (positionInputSeq && positionOutputSeq), + // then (way == std::ios_base::beg || way == std::ios_base::end) and + // therefore 'ptr' won't be used. + std::streamoff refOffset; + static_assert(sizeof(std::streamoff) >= sizeof(std::ptrdiff_t), + "Unexpected: sizeof(std::streamoff) < sizeof(std::ptrdiff_t)"); + static_assert(sizeof(std::streamoff) >= sizeof(std::size_t), + "Unexpected: sizeof(std::streamoff) < sizeof(std::size_t)"); + + if (way == std::ios_base::beg) { + refOffset = 0; + } else if (way == std::ios_base::cur) { + refOffset = static_cast(ptr - _buf); + } else { + assert(way == std::ios_base::end); + refOffset = static_cast(_bufSize); + } + + // Offset, relatively to _buf, where we are supposed to seek + std::streamoff totalOffset = refOffset + off; + typedef typename std::make_unsigned::type uStreamOff; + + if (totalOffset < 0 || static_cast(totalOffset) > _bufSize) { + return std::streampos(std::streamoff(-1)); + } else { + // Safe because 0 <= totalOffset <= _bufSize, which is an std::size_t + char* newPtr = _buf + static_cast(totalOffset); + + if (positionInputSeq) { + // eback() == _buf and egptr() == _buf + _bufSize + setg(eback(), newPtr, egptr()); + } + + if (positionOutputSeq) { + // epptr() == _buf + _bufSize + setp(newPtr, epptr()); + } + + // C++11's §27.8.2.4 item 12 (for stringbuf) would return refOffset. This + // makes no sense IMHO, in particular when 'way' is std::ios_base::beg or + // std::ios_base::end. Return the new offset (from the beginning of + // '_buf') instead. Note that this doesn't violate anything, because + // §27.6.3.4.2 grants full freedom as to the semantics of seekoff() to + // classes derived from basic_streambuf. + // + // My interpretation is consistent with items 13 and 14 of §27.8.2.4 + // concerning seekpos(), whereas item 12 is not (if item 12 were followed + // to the letter, seekoff() would always return 0 on success when + // way == std::ios_base::beg, and therefore items 13 and 14 would be + // incompatible). + return std::streampos(totalOffset); + } +} + +std::streampos CharArrayStreambuf::seekpos(std::streampos pos, + std::ios_base::openmode which) +{ + return seekoff(std::streamoff(pos), std::ios_base::beg, which); +} + +// *************************************************************************** +// * ROCharArrayStreambuf class * +// *************************************************************************** +ROCharArrayStreambuf::ROCharArrayStreambuf(const char* buf, std::size_t bufSize) + : CharArrayStreambuf(const_cast(buf), bufSize) +{ } + +const char* ROCharArrayStreambuf::data() const +{ + return const_cast(CharArrayStreambuf::data()); +} + +int ROCharArrayStreambuf::overflow(int c) +{ + return traits::eof(); // indicate failure +} + +std::streamsize ROCharArrayStreambuf::xsputn(const char* s, std::streamsize n) +{ + return 0; // number of chars written +} + +// *************************************************************************** +// * CharArrayIStream class * +// *************************************************************************** + +CharArrayIStream::CharArrayIStream(const char* buf, std::size_t bufSize) + : std::istream(nullptr), + _streamBuf(buf, bufSize) +{ + // Associate _streamBuf to 'this' and clear the error state flags + rdbuf(&_streamBuf); +} + +const char* CharArrayIStream::data() const +{ + return _streamBuf.data(); +} + +std::size_t CharArrayIStream::size() const +{ + return _streamBuf.size(); +} + +// *************************************************************************** +// * CharArrayOStream class * +// *************************************************************************** + +CharArrayOStream::CharArrayOStream(char* buf, std::size_t bufSize) + : std::ostream(nullptr), + _streamBuf(buf, bufSize) +{ + // Associate _streamBuf to 'this' and clear the error state flags + rdbuf(&_streamBuf); +} + +char* CharArrayOStream::data() const +{ + return _streamBuf.data(); +} + +std::size_t CharArrayOStream::size() const +{ + return _streamBuf.size(); +} + +// *************************************************************************** +// * CharArrayIOStream class * +// *************************************************************************** + +CharArrayIOStream::CharArrayIOStream(char* buf, std::size_t bufSize) + : std::iostream(nullptr), + _streamBuf(buf, bufSize) +{ + // Associate _streamBuf to 'this' and clear the error state flags + rdbuf(&_streamBuf); +} + +char* CharArrayIOStream::data() const +{ + return _streamBuf.data(); +} + +std::size_t CharArrayIOStream::size() const +{ + return _streamBuf.size(); +} + +} // of namespace simgear diff --git a/simgear/io/iostreams/CharArrayStream.hxx b/simgear/io/iostreams/CharArrayStream.hxx new file mode 100644 index 00000000..bae74340 --- /dev/null +++ b/simgear/io/iostreams/CharArrayStream.hxx @@ -0,0 +1,169 @@ +// -*- coding: utf-8 -*- +// +// CharArrayStream.hxx --- IOStreams classes for reading from, and writing to +// char arrays +// +// Copyright (C) 2017 Florent Rougon +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library 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 +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301 USA. + +#ifndef SG_CHAR_ARRAY_STREAM_HXX +#define SG_CHAR_ARRAY_STREAM_HXX + +#include +#include +#include +#include // std::streamsize, std::streampos... +#include // std::size_t + +// Contrary to std::stringstream and its (i/o)stringstream friends, the +// classes in this file allow one to work on an array of char (that could be +// for instance static data) without having to make a whole copy of it. +// +// There are five classes defined here in the 'simgear' namespace: +// - CharArrayStreambuf subclass of std::streambuf stream buffer +// - ROCharArrayStreambuf subclass of CharArrayStreambuf stream buffer +// - CharArrayIStream subclass of std::istream input stream +// - CharArrayOStream subclass of std::ostream output stream +// - CharArrayIOStream subclass of std::iostream input/output stream +// +// The main class is CharArrayStreambuf. ROCharArrayStreambuf is a read-only +// subclass of CharArrayStreambuf. The other three are very simple convenience +// classes, using either CharArrayStreambuf or ROCharArrayStreambuf as their +// stream buffer class. One can easily work with CharArrayStreambuf or +// ROCharArrayStreambuf only, either directly or after attaching an instance +// to an std::istream, std::ostream or std::iostream instance (using for +// example constructors like std::istream(std::streambuf* sb) or the rdbuf() +// method of stream classes). + +namespace simgear +{ + +// Input/output stream buffer class that reads from, and writes to a fixed +// buffer in memory specified in the constructor. This buffer must remain +// alive as long as the stream buffer object is used (the CharArrayStreambuf +// class works directly on that buffer without making any copy of it). +// +// Because reads and writes are directly performed on the buffer specified in +// the constructor, this stream buffer class has no caching behavior. You may +// use pubsync() if you like, but that is completely useless by design (it +// uses the default implementation in std::streambuf, which does nothing). +// +// CharArrayStreambuf may share similarities in features with +// std::strstreambuf (deprecated since C++98). However, at least one big +// difference is that CharArrayStreambuf does no dynamic memory allocation +// whatsoever. It works on a fixed-size-fixed-location buffer passed in the +// constructor, and nothing more. It does prevent overflowing the buffer, +// since it knows perfectly well where the buffer starts and ends. +class CharArrayStreambuf: public std::streambuf +{ +public: + explicit CharArrayStreambuf(char* buf, std::size_t bufSize); + + // Accessors for the buffer start pointer and size (same method names as for + // std::string) + char* data() const; + std::size_t size() const; + +protected: + virtual int underflow() override; + virtual int overflow(int c = std::char_traits::eof()) override; + // Optional override when subclassing std::streambuf. This is the most + // efficient way of reading several characters. + virtual std::streamsize xsgetn(char* dest, std::streamsize n) override; + // Ditto for writing + virtual std::streamsize xsputn(const char* s, std::streamsize n) override; + virtual std::streamsize showmanyc() override; + virtual std::streampos seekoff( + std::streamoff off, + std::ios_base::seekdir way, + std::ios_base::openmode which = std::ios_base::in | std::ios_base::out) + override; + virtual std::streampos seekpos( + std::streampos pos, + std::ios_base::openmode which = std::ios_base::in | std::ios_base::out) + override; + +private: + // These two define the buffer managed by the CharArrayStreambuf instance. + char* const _buf; + const std::size_t _bufSize; +}; + +// Read-only version of CharArrayStreambuf +class ROCharArrayStreambuf: public CharArrayStreambuf +{ +public: + explicit ROCharArrayStreambuf(const char* buf, std::size_t bufSize); + + // Accessor for the buffer start pointer (same method name as for + // std::string) + const char* data() const; + +private: + // Override methods pertaining to write access + virtual int overflow(int c = std::char_traits::eof()) override; + virtual std::streamsize xsputn(const char* s, std::streamsize n) override; +}; + +// Convenience class: std::istream subclass based on ROCharArrayStreambuf +class CharArrayIStream: public std::istream +{ +public: + // Same parameters as for ROCharArrayStreambuf + explicit CharArrayIStream(const char* buf, std::size_t bufSize); + + // Accessors for the underlying buffer start pointer and size + const char* data() const; + std::size_t size() const; + +private: + ROCharArrayStreambuf _streamBuf; +}; + +// Convenience class: std::ostream subclass based on CharArrayStreambuf +class CharArrayOStream: public std::ostream +{ +public: + // Same parameters as for CharArrayStreambuf + explicit CharArrayOStream(char* buf, std::size_t bufSize); + + // Accessors for the underlying buffer start pointer and size + char* data() const; + std::size_t size() const; + +private: + CharArrayStreambuf _streamBuf; +}; + +// Convenience class: std::iostream subclass based on CharArrayStreambuf +class CharArrayIOStream: public std::iostream +{ +public: + // Same parameters as for CharArrayStreambuf + explicit CharArrayIOStream(char* buf, std::size_t bufSize); + + // Accessors for the underlying buffer start pointer and size + char* data() const; + std::size_t size() const; + +private: + CharArrayStreambuf _streamBuf; +}; + +} // of namespace simgear + +#endif // of SG_CHAR_ARRAY_STREAM_HXX diff --git a/simgear/io/iostreams/CharArrayStream_test.cxx b/simgear/io/iostreams/CharArrayStream_test.cxx new file mode 100644 index 00000000..1f6870b1 --- /dev/null +++ b/simgear/io/iostreams/CharArrayStream_test.cxx @@ -0,0 +1,432 @@ +// -*- coding: utf-8 -*- +// +// CharArrayStream_test.cxx --- Automated tests for CharArrayStream.cxx +// +// Copyright (C) 2017 Florent Rougon +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library 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 +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +// MA 02110-1301 USA. + +#include + +#include // std::basic_ios, std::streamsize... +#include // std::ios_base, std::cerr, etc. +#include +#include // std::numeric_limits +#include // std::make_unsigned() +#include // std::unique_ptr +#include +#include // EXIT_SUCCESS +#include // std::size_t +#include // std::strlen() +#include // std::fill_n() +#include + +#include +#include "CharArrayStream.hxx" + +using std::string; +using std::cout; +using std::cerr; +using traits = std::char_traits; + +typedef typename std::make_unsigned::type uStreamSize; + + +// Safely convert a non-negative std::streamsize into an std::size_t. If +// impossible, bail out. +static std::size_t streamsizeToSize_t(std::streamsize n) +{ + SG_CHECK_GE(n, 0); + SG_CHECK_LE(static_cast(n), + std::numeric_limits::max()); + + return static_cast(n); +} + +void test_CharArrayStreambuf_basicOperations() +{ + cerr << "Testing basic operations on CharArrayStreambuf\n"; + + const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK " + "LMNOPQ"; + std::istringstream text_ss(text); + string canary = "YoY"; + // Reserve space for our little canary + const std::size_t bufSize = text.size() + canary.size(); + std::unique_ptr buf(new char[bufSize]); + int ch; + std::streamsize n; + std::streampos pos; + + // Only allow arraySBuf to read from, and write to the first text.size() + // chars of 'buf' + simgear::CharArrayStreambuf arraySBuf(&buf[0], text.size()); + + // 1) Write a copy of the 'text' string at buf.get(), testing various write + // and seek methods. + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // + // Write "01" with two sputc() calls + ch = arraySBuf.sputc(text[0]); + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '0' && buf[0] == '0'); + ch = arraySBuf.sputc(text[1]); + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '1' && buf[1] == '1'); + // Write the 34 following chars of 'text' with one sputn() call + n = arraySBuf.sputn(&text[2], 34); + SG_CHECK_EQUAL(n, 34); + SG_CHECK_EQUAL(string(&buf[2], 34), "23456789abcdefghijklmnopqrstuvwxyz"); + + // Indirect test of seekpos(): position the write stream pointer a bit further + pos = arraySBuf.pubseekpos(43, std::ios_base::out); + SG_CHECK_EQUAL(pos, 43); + // Write 7 more chars with sputn() + n = arraySBuf.sputn(&text[43], 7); + SG_CHECK_EQUAL(n, 7); + SG_CHECK_EQUAL(string(&buf[43], 7), "\nGHIJK "); + // Indirect test of seekoff(): seek backwards relatively to the current write + // pointer position + pos = arraySBuf.pubseekoff(-std::strlen("\nABCDEF\nGHIJK "), + std::ios_base::cur, std::ios_base::out); + SG_CHECK_EQUAL(pos, 36); // 10 + 26, i.e., after the lowercase alphabet + // Write "\nABCD" to buf in one sputn() call + n = arraySBuf.sputn(&text[36], 5); + // Now write "EF" in two sputc() calls + ch = arraySBuf.sputc(text[41]); + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'E' && buf[41] == 'E'); + ch = arraySBuf.sputc(text[42]); + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'F' && buf[42] == 'F'); + + // Place a canary to check that arraySBuf doesn't write beyond the end of buf + std::copy(canary.begin(), canary.end(), &buf[text.size()]); + + // Check seeking from arraySBuf's end (which is at offset text.size(), *not* + // bufSize: cf. the construction of arraySBuf!). + pos = arraySBuf.pubseekoff(-std::streamoff(std::strlen("LMNOPQ")), + std::ios_base::end, std::ios_base::out); + SG_CHECK_EQUAL(pos, std::streampos(text.size() - std::strlen("LMNOPQ"))); + // Write "LMNOPQ" to buf in one sputn() call. The other characters won't be + // written, because they would go past the end of the buffer managed by + // 'arraySBuf' (i.e., the first text.size() chars of 'buf'). + static const char someChars[] = "LMNOPQ+buffer overrun that will be blocked"; + n = arraySBuf.sputn(someChars, sizeof(someChars)); + + // Check the number of chars actually written + SG_CHECK_EQUAL(n, std::strlen("LMNOPQ")); + // Check that our canary starting at buf[text.size()] is still there and + // intact + SG_CHECK_EQUAL(string(&buf[text.size()], canary.size()), canary); + // Check that we now have an exact copy of 'text' in the managed buffer + SG_CHECK_EQUAL(string(&buf[0], text.size()), text); + + // 2) Read back the copy of 'text' in 'buf', using various read and seek + // methods. + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // + ch = arraySBuf.sgetc(); + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '0'); + ch = arraySBuf.sbumpc(); + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '0'); + ch = arraySBuf.sbumpc(); + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '1'); + ch = arraySBuf.snextc(); + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '3'); + ch = arraySBuf.sbumpc(); + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '3'); + ch = arraySBuf.sbumpc(); + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '4'); + ch = arraySBuf.sputbackc('4'); + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '4'); + ch = arraySBuf.sputbackc('u'); // doesn't match what we read from the stream + SG_VERIFY(ch == EOF); + ch = arraySBuf.sputbackc('3'); // this one does + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == '3'); + + static constexpr std::streamsize buf2Size = 10; + char buf2[buf2Size]; + // Most efficient way (with the underlying xsgetn()) to read several chars + // at once. + n = arraySBuf.sgetn(buf2, buf2Size); + SG_CHECK_EQUAL(n, buf2Size); + SG_CHECK_EQUAL(string(buf2, static_cast(buf2Size)), + "3456789abc"); + + ch = arraySBuf.sungetc(); // same as sputbackc(), except no value to check + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'c'); + ch = arraySBuf.sungetc(); + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'b'); + ch = arraySBuf.sbumpc(); + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'b'); + ch = arraySBuf.sputbackc('b'); // this one does + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'b'); + + n = arraySBuf.sgetn(buf2, buf2Size); + SG_CHECK_EQUAL(n, buf2Size); + SG_CHECK_EQUAL(string(buf2, static_cast(buf2Size)), + "bcdefghijk"); + + ch = arraySBuf.sungetc(); + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == 'k'); + + static char buf3[64]; + n = arraySBuf.sgetn(buf3, sizeof(buf3)); + SG_CHECK_EQUAL(n, 36); + SG_CHECK_EQUAL(string(buf3, 36), "klmnopqrstuvwxyz\nABCDEF\nGHIJK LMNOPQ"); + + SG_CHECK_EQUAL(arraySBuf.sbumpc(), EOF); + + // Check we can independently set the read and write pointers for arraySBuf + pos = arraySBuf.pubseekpos(10, std::ios_base::in); + SG_CHECK_EQUAL(pos, 10); + pos = arraySBuf.pubseekpos(13, std::ios_base::out); + SG_CHECK_EQUAL(pos, 13); + + // Write "DEF" where there is currently "def" in 'buf'. + for (int i = 0; i < 3; i++) { + char c = 'D' + i; + ch = arraySBuf.sputc(c); + SG_VERIFY(ch != EOF && traits::to_char_type(ch) == c && buf[i+13] == c); + } + + n = arraySBuf.sgetn(buf3, 6); + SG_CHECK_EQUAL(n, 6); + SG_CHECK_EQUAL(string(buf3, 6), "abcDEF"); + + // Set both stream pointers at once (read and write) + pos = arraySBuf.pubseekpos(10, std::ios_base::in | std::ios_base::out); + SG_VERIFY(pos == 10); + + // Write "ABC" to buf in one sputn() call + n = arraySBuf.sputn("ABC", 3); + SG_CHECK_EQUAL(n, 3); + SG_CHECK_EQUAL(string(&buf[10], 3), "ABC"); + + // Indirect test of seekoff(): seek backwards relatively to the current read + // pointer position + pos = arraySBuf.pubseekoff(-3, std::ios_base::cur, std::ios_base::in); + SG_CHECK_EQUAL(pos, 7); + + n = arraySBuf.sgetn(buf3, 12); + SG_CHECK_EQUAL(n, 12); + SG_CHECK_EQUAL(string(buf3, 12), "789ABCDEFghi"); +} + +void test_CharArrayStreambuf_readOrWriteLargestPossibleAmount() +{ + cerr << "Testing reading and writing from/to CharArrayStreambuf with the " + "largest possible value passed as the 'n' argument for sgetn()/sputn() " + "(number of chars to read or write)\n"; + + const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK " + "LMNOPQ"; + string canary = "ZaZ"; + // Reserve space for our little canary + const std::size_t bufSize = text.size() + canary.size(); + std::unique_ptr buf(new char[bufSize]); + std::streamsize n_s; + std::size_t n; + std::streampos pos; + + // Place a canary to check that arraySBuf doesn't write beyond the end of buf + std::copy(canary.begin(), canary.end(), &buf[text.size()]); + + // Only allow arraySBuf to read from, and write to the first text.size() + // chars of 'buf' + simgear::CharArrayStreambuf arraySBuf(&buf[0], text.size()); + + n_s = arraySBuf.sputn(text.c_str(), + std::numeric_limits::max()); + // The conversion to std::size_t is safe because arraySBuf.sputn() returns a + // non-negative value which, in this case, can't exceed the size of the + // buffer managed by 'arraySBuf', i.e. text.size(). + n = streamsizeToSize_t(n_s); + SG_CHECK_EQUAL(n, arraySBuf.size()); + SG_CHECK_EQUAL(n, text.size()); + SG_CHECK_EQUAL(string(&buf[0], n), text); + + // Check that our canary starting at &buf[text.size()] is still there and + // intact + SG_CHECK_EQUAL(string(&buf[text.size()], canary.size()), canary); + + // The “get” stream pointer is still at the beginning of the buffer managed + // by 'arraySBuf'. Let's ask for the maximum amount of chars from it to be + // written to a new buffer, 'buf2'. + std::unique_ptr buf2(new char[text.size()]); + n_s = arraySBuf.sgetn(&buf2[0], + std::numeric_limits::max()); + // The conversion to std::size_t is safe because arraySBuf.sgetn() returns a + // non-negative value which, in this case, can't exceed the size of the + // buffer managed by 'arraySBuf', i.e. text.size(). + n = streamsizeToSize_t(n_s); + SG_CHECK_EQUAL(n, arraySBuf.size()); + SG_CHECK_EQUAL(string(&buf2[0], n), text); + + SG_CHECK_EQUAL(arraySBuf.sbumpc(), EOF); +} + +void test_CharArrayIStream_simple() +{ + // This also tests ROCharArrayStreambuf, since it is used as + // CharArrayIStream's stream buffer class. + cerr << "Testing read operations from CharArrayIStream\n"; + + const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK " + "LMNOPQ"; + std::unique_ptr buf(new char[text.size()]); + std::size_t n; + + simgear::CharArrayIStream caStream(&text[0], text.size()); + caStream.exceptions(std::ios_base::badbit); // throw if badbit is set + + SG_CHECK_EQUAL(caStream.data(), &text[0]); + SG_CHECK_EQUAL(caStream.size(), text.size()); + + SG_VERIFY(caStream.get(buf[0])); // get pointer = 1 + SG_CHECK_EQUAL(buf[0], text[0]); + + caStream.putback(buf[0]); // get pointer = 0 + SG_CHECK_EQUAL(caStream.get(), traits::to_int_type(text[0])); // get ptr = 1 + + // std::iostream::operator bool() will return false due to EOF being reached + SG_VERIFY(!caStream.read(&buf[1], + std::numeric_limits::max())); + // If badbit had been set, it would have caused an exception to be raised + SG_VERIFY(caStream.eof() && caStream.fail() && !caStream.bad()); + + // The conversion to std::size_t is safe because caStream.gcount() returns a + // non-negative value which, in this case, can't exceed the size of the + // buffer managed by caStream's associated stream buffer, i.e. text.size(). + n = streamsizeToSize_t(caStream.gcount()); + SG_CHECK_EQUAL(n, caStream.size() - 1); + SG_CHECK_EQUAL(string(caStream.data(), caStream.size()), text); + + SG_CHECK_EQUAL(caStream.get(), EOF); + SG_VERIFY(caStream.eof() && caStream.fail() && !caStream.bad()); + + // Test stream extraction: operator>>() + caStream.clear(); // clear the error state flags + SG_VERIFY(caStream.seekg(0)); // rewind + std::vector expectedWords = { + "0123456789abcdefghijklmnopqrstuvwxyz", + "ABCDEF", + "GHIJK", + "LMNOPQ" + }; + string str; + + for (int i = 0; caStream >> str; i++) { + SG_CHECK_EQUAL(str, expectedWords[i]); + } + + SG_VERIFY(caStream.eof() && caStream.fail() && !caStream.bad()); +} + +void test_CharArrayOStream_simple() +{ + cerr << "Testing write operations to CharArrayOStream\n"; + + const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK " + "LMNOPQ"; + // One could also use an std::vector, but then beware of reallocations! + std::unique_ptr buf(new char[text.size()]); + std::fill_n(buf.get(), text.size(), '\0'); // to ensure reproducible results + + simgear::CharArrayOStream caStream(&buf[0], text.size()); + + SG_CHECK_EQUAL(caStream.data(), &buf[0]); + SG_CHECK_EQUAL(caStream.size(), text.size()); + + SG_VERIFY(caStream.put(text[0])); // buf[0] = text[0], put pointer = 1 + SG_CHECK_EQUAL(buf[0], text[0]); + + SG_VERIFY(caStream.seekp(8)); // put pointer = 8 + // buf[8:23] = text[8:23] (meaning: buf[8] = text[8], ..., buf[22] = text[22]) + SG_VERIFY(caStream.write(&text[8], 15)); // put pointer = 23 + buf[1] = 'X'; // write garbage to buf[1] + buf[2] = 'Y'; // and to buf[2] + SG_VERIFY(caStream.seekp(-22, std::ios_base::cur)); // put pointer = 23-22 = 1 + SG_VERIFY(caStream.write(&text[1], 7)); // buf[1:8] = text[1:8] + + // The std::ios_base::beg argument is superfluous here---just for testing. + SG_VERIFY(caStream.seekp(23, std::ios_base::beg)); // put pointer = 23 + // Test stream insertion: operator<<() + SG_VERIFY(caStream << text.substr(23, 10)); + SG_VERIFY(caStream.write(&text[33], text.size() - 33)); // all that remains + SG_VERIFY(!caStream.put('Z')); // doesn't fit in caStream's buffer + SG_VERIFY(caStream.bad()); // put() set the stream's badbit flag + + SG_CHECK_EQUAL(string(caStream.data(), caStream.size()), text); +} + +void test_CharArrayIOStream_readWriteSeekPutbackEtc() +{ + cerr << "Testing read, write, seek, putback... from/to CharArrayIOStream\n"; + + const string text = "0123456789abcdefghijklmnopqrstuvwxyz\nABCDEF\nGHIJK " + "LMNOPQ"; + std::unique_ptr buf(new char[text.size()]); + std::size_t n; + char ch; + + simgear::CharArrayIOStream caStream(&buf[0], text.size()); + caStream.exceptions(std::ios_base::badbit); // throw if badbit is set + + SG_CHECK_EQUAL(caStream.data(), &buf[0]); + SG_CHECK_EQUAL(caStream.size(), text.size()); + + SG_VERIFY(caStream.put(text[0])); // buf[0] = text[0], put pointer = 1 + SG_CHECK_EQUAL(buf[0], text[0]); + SG_VERIFY(caStream.get(ch)); // read it back from buf, get pointer = 1 + SG_CHECK_EQUAL(ch, text[0]); + + caStream.putback(buf[0]); // get pointer = 0 + SG_CHECK_EQUAL(caStream.get(), traits::to_int_type(text[0])); // get ptr = 1 + + SG_VERIFY(caStream.seekp(5)); + // buf[5:10] = text[5:10] (meaning: buf[5] = text[5], ..., buf[9] = text[9]) + SG_VERIFY(caStream.write(&text[5], 5)); // put pointer = 10 + buf[1] = 'X'; // write garbage to buf[1] + buf[2] = 'Y'; // and to buf[2] + SG_VERIFY(caStream.seekp(-9, std::ios_base::cur)); // put pointer = 10 - 9 = 1 + SG_VERIFY(caStream.write(&text[1], 4)); // buf[1:5] = text[1:5] + + SG_VERIFY(caStream.seekg(10)); // get pointer = 10 + // std::iostream::operator bool() will return false due to EOF being reached + SG_VERIFY(!caStream.read(&buf[10], + std::numeric_limits::max())); + // If badbit had been set, it would have caused an exception to be raised + SG_VERIFY(caStream.eof() && caStream.fail() && !caStream.bad()); + + // The conversion to std::size_t is safe because caStream.gcount() returns a + // non-negative value which, in this case, can't exceed the size of the + // buffer managed by caStream's associated stream buffer, i.e. text.size(). + n = streamsizeToSize_t(caStream.gcount()); + SG_CHECK_EQUAL(n, caStream.size() - 10); + SG_CHECK_EQUAL(string(caStream.data(), caStream.size()), text); + + SG_CHECK_EQUAL(caStream.get(), EOF); + SG_VERIFY(caStream.eof() && caStream.fail() && !caStream.bad()); +} + +int main(int argc, char** argv) +{ + test_CharArrayStreambuf_basicOperations(); + test_CharArrayStreambuf_readOrWriteLargestPossibleAmount(); + test_CharArrayIStream_simple(); + test_CharArrayOStream_simple(); + test_CharArrayIOStream_readWriteSeekPutbackEtc(); + + return EXIT_SUCCESS; +}