Add CharArrayStreambuf and related IOStreams classes for working with char arrays
Add the following classes 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 CharArrayStreambuf is a stream buffer class allowing to read from, and write to char arrays (std::strstream has been deprecated since C++98). Contrary to std::strstream, this class does no dynamic allocation: it is very simple, strictly staying for both reads and writes within the limits of the buffer specified in the constructor. Contrary to std::stringstream, CharArrayStreambuf allows one to work on an array of char (that could be static data or on the stack) without having to make a whole copy of it. CharArrayStreambuf supports reading and writing (including efficient implementations of xsgetn() and xsputn()), seeking (with independent read and write stream pointers, as for std::stringstream) and putting back chars up to the beginning of the char array. The internal buffer for both reads and writes is defined to be the whole buffer specified in the constructor call. As a consequence, flushing the stream buffer with pubsync() is useless: data is always written directly to the buffer passed to the constructor, never to an intermediate buffer. Of course, this buffer must remain available as long as the stream buffer object is used. ROCharArrayStreambuf is a read-only subclass of CharArrayStreambuf. CharArrayIStream, CharArrayOStream and CharArrayIOStream are very simple convenience stream classes using either CharArrayStreambuf or ROCharArrayStreambuf as their stream buffer class.
This commit is contained in:
@@ -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)
|
||||
|
||||
291
simgear/io/iostreams/CharArrayStream.cxx
Normal file
291
simgear/io/iostreams/CharArrayStream.cxx
Normal file
@@ -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 <simgear_config.h>
|
||||
|
||||
#include <ios> // std::streamsize
|
||||
#include <istream> // std::istream and std::iostream
|
||||
#include <ostream> // std::ostream
|
||||
#include <type_traits> // std::make_unsigned()
|
||||
#include <cstddef> // std::size_t, std::ptrdiff_t
|
||||
#include <cassert>
|
||||
|
||||
#include "CharArrayStream.hxx"
|
||||
|
||||
using traits = std::char_traits<char>;
|
||||
|
||||
|
||||
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<std::ptrdiff_t>(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<std::streamsize>(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<std::ptrdiff_t>(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<std::streamsize>(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<std::streamoff>(ptr - _buf);
|
||||
} else {
|
||||
assert(way == std::ios_base::end);
|
||||
refOffset = static_cast<std::streamoff>(_bufSize);
|
||||
}
|
||||
|
||||
// Offset, relatively to _buf, where we are supposed to seek
|
||||
std::streamoff totalOffset = refOffset + off;
|
||||
typedef typename std::make_unsigned<std::streamoff>::type uStreamOff;
|
||||
|
||||
if (totalOffset < 0 || static_cast<uStreamOff>(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<std::size_t>(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<char*>(buf), bufSize)
|
||||
{ }
|
||||
|
||||
const char* ROCharArrayStreambuf::data() const
|
||||
{
|
||||
return const_cast<const char*>(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
|
||||
169
simgear/io/iostreams/CharArrayStream.hxx
Normal file
169
simgear/io/iostreams/CharArrayStream.hxx
Normal file
@@ -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 <istream>
|
||||
#include <ostream>
|
||||
#include <streambuf>
|
||||
#include <ios> // std::streamsize, std::streampos...
|
||||
#include <cstddef> // 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<char>::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<char>::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
|
||||
432
simgear/io/iostreams/CharArrayStream_test.cxx
Normal file
432
simgear/io/iostreams/CharArrayStream_test.cxx
Normal file
@@ -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 <simgear_config.h>
|
||||
|
||||
#include <ios> // std::basic_ios, std::streamsize...
|
||||
#include <iostream> // std::ios_base, std::cerr, etc.
|
||||
#include <sstream>
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <type_traits> // std::make_unsigned()
|
||||
#include <memory> // std::unique_ptr
|
||||
#include <cassert>
|
||||
#include <cstdlib> // EXIT_SUCCESS
|
||||
#include <cstddef> // std::size_t
|
||||
#include <cstring> // std::strlen()
|
||||
#include <algorithm> // std::fill_n()
|
||||
#include <vector>
|
||||
|
||||
#include <simgear/misc/test_macros.hxx>
|
||||
#include "CharArrayStream.hxx"
|
||||
|
||||
using std::string;
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using traits = std::char_traits<char>;
|
||||
|
||||
typedef typename std::make_unsigned<std::streamsize>::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<uStreamSize>(n),
|
||||
std::numeric_limits<std::size_t>::max());
|
||||
|
||||
return static_cast<std::size_t>(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<char[]> 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<std::size_t>(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<std::size_t>(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<char[]> 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<std::streamsize>::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<char[]> buf2(new char[text.size()]);
|
||||
n_s = arraySBuf.sgetn(&buf2[0],
|
||||
std::numeric_limits<std::streamsize>::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<char[]> 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<std::streamsize>::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<string> 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<char>, but then beware of reallocations!
|
||||
std::unique_ptr<char[]> 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<char[]> 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<std::streamsize>::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;
|
||||
}
|
||||
Reference in New Issue
Block a user