Add basic binary serialization library with tests

This commit is contained in:
Nikita Lisitsa 2022-06-13 00:17:58 +03:00
parent 9a47155284
commit d4f299f277
13 changed files with 1440 additions and 0 deletions

8
libs/sir/CMakeLists.txt Normal file
View file

@ -0,0 +1,8 @@
file(GLOB_RECURSE PSEMEK_SIR_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp")
file(GLOB_RECURSE PSEMEK_SIR_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp")
psemek_add_library(psemek-sir ${PSEMEK_SIR_HEADERS} ${PSEMEK_SIR_SOURCES})
target_include_directories(psemek-sir PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(psemek-sir PUBLIC psemek-io)
psemek_glob_tests(psemek-sir tests)

View file

@ -0,0 +1,197 @@
#pragma once
#include <psemek/sir/platform.hpp>
#include <psemek/sir/stream.hpp>
#include <psemek/sir/trivial.hpp>
#include <psemek/sir/struct.hpp>
#include <array>
namespace psemek::sir
{
namespace detail
{
using std::begin;
using std::end;
using std::size;
using std::data;
template <typename Container>
constexpr bool is_container_helper = !is_struct_v<Container> && requires (std::remove_cv_t<Container> c)
{
begin(c);
end(c);
size(c);
requires requires (typename Container::value_type const & x) { c.insert(end(c), x); };
};
template <typename Container>
constexpr bool is_contiguous_container_helper = is_container_helper<Container> && requires (Container & c)
{
data(c);
};
template <typename Container>
constexpr bool is_associative_container_helper = is_container_helper<Container> && requires
{
typename Container::key_type;
typename Container::mapped_type;
};
}
using size_type = std::uint32_t;
template <typename Container>
struct is_container
: std::bool_constant<detail::is_container_helper<Container>>
{};
template <typename Container>
constexpr bool is_container_v = is_container<Container>::value;
template <typename Container>
struct is_contiguous_container
: std::bool_constant<detail::is_contiguous_container_helper<Container>>
{};
template <typename Container>
constexpr bool is_contiguous_container_v = is_contiguous_container<Container>::value;
template <typename Container>
struct is_associative_container
: std::bool_constant<detail::is_associative_container_helper<Container>>
{};
template <typename Container>
constexpr bool is_associative_container_v = is_associative_container<Container>::value;
template <typename Container>
struct has_static_size
: std::false_type
{};
template <typename Container>
constexpr bool has_static_size_v = has_static_size<Container>::value;
template <typename T, std::size_t N>
struct has_static_size<T[N]>
: std::true_type
{};
template <typename T, std::size_t N>
struct has_static_size<std::array<T, N>>
: std::true_type
{};
template <typename Stream, typename Container>
requires is_ostream_v<Stream>
void write_size(Stream & s, Container const & c)
{
write(s, static_cast<size_type>(std::size(c)));
}
template <typename Stream, typename Container>
requires is_istream_v<Stream>
void read_size(Stream & s, Container & c)
{
size_type size;
read(s, size);
c.resize(size);
}
template <typename Stream, typename T>
requires (is_ostream_v<Stream> && is_trivial_v<T>)
void write_contiguous(Stream & s, T const * begin, std::size_t size)
{
write_padding<T>(s);
s.write(reinterpret_cast<char const *>(begin), sizeof(T) * size);
}
template <typename Stream, typename T>
requires (is_istream_v<Stream> && is_trivial_v<T>)
void read_contiguous(Stream & s, T * begin, std::size_t size)
{
read_padding<T>(s);
s.read(reinterpret_cast<char *>(begin), sizeof(T) * size);
}
template <typename Stream, typename Container>
requires is_ostream_v<Stream>
void write_container(Stream & s, Container const & c)
{
if constexpr (!has_static_size_v<Container>)
write_size(s, c);
using std::begin;
using std::end;
using std::data;
using std::size;
if constexpr (is_contiguous_container_v<Container> && is_trivial_v<std::decay_t<decltype(*begin(c))>>)
write_contiguous(s, data(c), size(c));
else
write_sequence(s, begin(c), end(c));
}
template <typename Stream, typename Container>
requires is_istream_v<Stream>
void read_container(Stream & s, Container & c)
{
using std::begin;
using std::end;
using std::data;
using std::size;
using value_type = std::decay_t<decltype(*begin(c))>;
if constexpr (has_static_size_v<Container>)
read_sequence(s, begin(c), end(c));
else if constexpr (is_contiguous_container_v<Container> && is_trivial_v<value_type>)
{
read_size(s, c);
read_contiguous(s, data(c), size(c));
}
else if constexpr (is_associative_container_v<Container>)
{
size_type size;
read(s, size);
while (size --> 0)
{
typename Container::key_type key;
typename Container::mapped_type value;
read(s, key);
read(s, value);
c.insert(end(c), {std::move(key), std::move(value)});
}
}
else
{
size_type size;
read(s, size);
while (size --> 0)
{
value_type x;
read(s, x);
c.insert(end(c), std::move(x));
}
}
}
template <typename Stream, typename T>
requires (is_ostream_v<Stream> && !is_custom_v<T> && !is_empty_v<T> && !is_trivial_v<T> && !is_struct_v<T> && is_container_v<T>)
void write(Stream & s, T const & x)
{
write_container(s, x);
}
template <typename Stream, typename T>
requires (is_istream_v<Stream> && !is_custom_v<T> && !is_empty_v<T> && !is_trivial_v<T> && !is_struct_v<T> && is_container_v<T>)
void read(Stream & s, T & x)
{
read_container(s, x);
}
}

View file

@ -0,0 +1,295 @@
#pragma once
#include <psemek/sir/stream.hpp>
#include <psemek/sir/trivial.hpp>
#include <psemek/sir/container.hpp>
#include <psemek/io/memory_stream.hpp>
namespace psemek::sir
{
struct memory_istream
: istream
{
memory_istream(io::memory_istream & s, std::size_t offset = 0)
: istream(s, offset)
{}
char const * data() const
{
return static_cast<io::memory_istream const &>(stream()).data();
}
void advance(std::size_t n)
{
static_cast<io::memory_istream &>(stream()).advance(n);
}
private:
std::string_view data_;
std::size_t offset_;
};
static_assert(is_istream_v<memory_istream>);
namespace detail
{
template <typename T>
struct vector
{
vector() = default;
vector(T const * begin, T const * end)
: begin_(begin)
, end_(end)
{}
vector(vector && v)
: begin_(v.begin_)
, end_(v.end_)
{
v.begin_ = nullptr;
v.end_ = nullptr;
}
vector(vector const &) = default;
vector & operator = (vector const &) = default;
vector & operator = (vector && v)
{
if (&v == this)
return *this;
begin_ = v.begin_;
end_ = v.end_;
v.begin_ = nullptr;
v.end_ = nullptr;
return *this;
}
T const * begin() const
{
return begin_;
}
T const * end() const
{
return end_;
}
std::size_t size() const
{
return end_ - begin_;
}
T const & operator[] (std::size_t i) const
{
return begin_[i];
}
T const & at(std::size_t i) const
{
if (i >= size())
throw std::out_of_range("psemek::sir::vector::at");
return begin_[i];
}
private:
T const * begin_ = nullptr;
T const * end_ = nullptr;
};
template <typename T>
requires is_trivial_v<T>
void read(memory_istream & s, vector<T> & x)
{
size_type size;
read(s, size);
read_padding<T>(s);
auto begin = reinterpret_cast<T const *>(s.data());
s.advance(size * sizeof(T));
auto end = reinterpret_cast<T const *>(s.data());
x = vector<T>(begin, end);
}
template <typename T, typename Compare = std::less<void>>
struct set
{
set() = default;
set(T const * begin, T const * end)
: begin_(begin)
, end_(end)
{}
set(set && s)
: begin_(s.begin_)
, end_(s.end_)
{
s.begin_ = nullptr;
s.end_ = nullptr;
}
set(set const &) = default;
set & operator = (set const &) = default;
set & operator = (set && s)
{
if (&s == this)
return *this;
begin_ = s.begin_;
end_ = s.end_;
s.begin_ = nullptr;
s.end_ = nullptr;
return *this;
}
T const * begin() const
{
return begin_;
}
T const * end() const
{
return end_;
}
std::size_t size() const
{
return end_ - begin_;
}
template <typename H>
T const * find(H const & x) const
{
Compare comp;
auto it = std::lower_bound(begin_, end_, x, comp);
if (it != end_ && comp(x, *it))
return end_;
return it;
}
private:
T const * begin_ = nullptr;
T const * end_ = nullptr;
};
template <typename T, typename Compare>
requires is_trivial_v<T>
void read(memory_istream & s, set<T, Compare> & x)
{
size_type size;
read(s, size);
read_padding<T>(s);
auto begin = reinterpret_cast<T const *>(s.data());
s.advance(size * sizeof(T));
auto end = reinterpret_cast<T const *>(s.data());
x = set<T, Compare>(begin, end);
}
template <typename K, typename V, typename Compare = std::less<void>>
struct map
{
struct pair
{
K first;
V second;
};
map() = default;
map(pair const * begin, pair const * end)
: begin_(begin)
, end_(end)
{}
map(map && m)
: begin_(m.begin_)
, end_(m.end_)
{
m.begin_ = nullptr;
m.end_ = nullptr;
}
map(map const &) = default;
map & operator = (map const &) = default;
map & operator = (map && m)
{
if (&m == this)
return *this;
begin_ = m.begin_;
end_ = m.end_;
m.begin_ = nullptr;
m.end_ = nullptr;
return *this;
}
pair const * begin() const
{
return begin_;
}
pair const * end() const
{
return end_;
}
std::size_t size() const
{
return end_ - begin_;
}
template <typename H>
pair const * find(H const & x) const
{
Compare comp;
auto it = std::lower_bound(begin_, end_, x, [&](auto const & p, auto const & x){ return comp(p.first, x); });
if (it != end_ && comp(x, it->first))
return end_;
return it;
}
V const & at(K const & key) const
{
auto p = find(key);
if (p == end_)
throw std::out_of_range("psemek::sir::map::at");
return p->second;
}
private:
pair const * begin_ = nullptr;
pair const * end_ = nullptr;
};
template <typename K, typename V, typename Compare>
requires is_trivial_v<typename map<K, V, Compare>::pair>
void read(memory_istream & s, map<K, V, Compare> & x)
{
using T = typename map<K, V, Compare>::pair;
size_type size;
read(s, size);
read_padding<T>(s);
auto begin = reinterpret_cast<T const *>(s.data());
s.advance(size * sizeof(T));
auto end = reinterpret_cast<T const *>(s.data());
x = map<K, V, Compare>(begin, end);
}
}
using detail::vector;
using detail::set;
using detail::map;
}

View file

@ -0,0 +1,32 @@
#pragma once
#include <bit>
#include <cstdint>
namespace psemek::sir
{
// If the target platform is big endian or mixed endian, the library needs tweaking
static_assert(std::endian::native == std::endian::little);
// If the target platform has different sizes or alignments for basic types, the library needs tweaking
static_assert(sizeof(char) == 1);
static_assert(alignof(char) == 1);
static_assert(sizeof(char8_t) == 1);
static_assert(alignof(char8_t) == 1);
static_assert(sizeof(char32_t) == 4);
static_assert(alignof(char32_t) == 4);
static_assert(alignof(std::uint8_t) == 1);
static_assert(alignof(std::uint16_t) == 2);
static_assert(alignof(std::uint32_t) == 4);
static_assert(alignof(std::uint64_t) == 8);
static_assert(alignof(std::int8_t) == 1);
static_assert(alignof(std::int16_t) == 2);
static_assert(alignof(std::int32_t) == 4);
static_assert(alignof(std::int64_t) == 8);
static_assert(sizeof(float) == 4);
static_assert(alignof(float) == 4);
static_assert(sizeof(double) == 8);
static_assert(alignof(double) == 8);
}

View file

@ -0,0 +1,128 @@
#pragma once
#include <psemek/io/stream.hpp>
namespace psemek::sir
{
namespace detail
{
template <typename T>
constexpr bool is_istream_helper = requires (T & x, T const & cx, char * p, std::size_t n)
{
x.read(p, n);
{ cx.offset() } -> std::same_as<std::size_t>;
};
template <typename T>
constexpr bool is_ostream_helper = requires (T & x, T const & cx, char const * p, std::size_t n)
{
x.write(p, n);
{ cx.offset() } -> std::same_as<std::size_t>;
};
}
template <typename T>
struct is_istream
: std::bool_constant<detail::is_istream_helper<T>>
{};
template <typename T>
constexpr bool is_istream_v = is_istream<T>::value;
template <typename T>
struct is_ostream
: std::bool_constant<detail::is_ostream_helper<T>>
{};
template <typename T>
constexpr bool is_ostream_v = is_ostream<T>::value;
struct istream
{
istream(io::istream & s, std::size_t offset = 0)
: s_(s)
, offset_(offset)
{}
void read(char * p, std::size_t size)
{
s_.read_all(p, size);
offset_ += size;
}
io::istream & stream() { return s_; }
io::istream const & stream() const { return s_; }
std::size_t offset() const { return offset_; }
private:
io::istream & s_;
std::size_t offset_ = 0;
};
struct ostream
{
ostream(io::ostream & s, std::size_t offset = 0)
: s_(s)
, offset_(offset)
{}
void write(char const * p, std::size_t size)
{
s_.write_all(p, size);
offset_ += size;
}
io::ostream & stream() { return s_; }
io::ostream const & stream() const { return s_; }
std::size_t offset() const { return offset_; }
private:
io::ostream & s_;
std::size_t offset_ = 0;
};
struct null_istream
{
null_istream(std::size_t offset = 0)
: offset_(offset)
{}
void read(char *, std::size_t size)
{
offset_ += size;
}
std::size_t offset() const { return offset_; }
private:
std::size_t offset_ = 0;
};
struct null_ostream
{
null_ostream(std::size_t offset = 0)
: offset_(offset)
{}
void write(char const *, std::size_t size)
{
offset_ += size;
}
std::size_t offset() const { return offset_; }
private:
std::size_t offset_ = 0;
};
static_assert(is_istream_v<istream>);
static_assert(is_ostream_v<ostream>);
static_assert(is_istream_v<null_istream>);
static_assert(is_ostream_v<null_ostream>);
}

View file

@ -0,0 +1,89 @@
#pragma once
#include <psemek/sir/trivial.hpp>
#include <utility>
namespace psemek::sir
{
namespace detail
{
template <typename T>
constexpr bool is_struct_helper = requires (T x)
{
std::get<0>(x);
std::tuple_size_v<T>;
};
template <typename T, std::size_t ... Is>
void write_struct_impl(ostream & s, T const & x, std::index_sequence<Is...>)
{
(write(s, std::get<Is>(x)), ...);
}
template <typename T, std::size_t ... Is>
void read_struct_impl(istream & s, T & x, std::index_sequence<Is...>)
{
(read(s, std::get<Is>(x)), ...);
}
}
template <typename T>
struct is_struct
: std::bool_constant<detail::is_struct_helper<T>>
{};
template <typename T>
constexpr bool is_struct_v = is_struct<T>::value;
template <typename T, std::size_t N>
struct is_struct<T[N]>
: std::true_type
{};
template <typename Stream, typename T>
requires is_ostream_v<Stream>
void write_struct(Stream & s, T const & x)
{
detail::write_struct_impl(s, x, std::make_index_sequence<std::tuple_size_v<T>>{});
}
template <typename Stream, typename T>
requires is_istream_v<Stream>
void read_struct(Stream & s, T & x)
{
detail::read_struct_impl(s, x, std::make_index_sequence<std::tuple_size_v<T>>{});
}
template <typename Stream, typename T, std::size_t N>
requires is_ostream_v<Stream>
void write_struct(ostream & s, T const (&x)[N])
{
write_sequence(s, x, x + N);
}
template <typename Stream, typename T, std::size_t N>
requires is_istream_v<Stream>
void read_struct(Stream & s, T (&x)[N])
{
read_sequence(s, x, x + N);
}
template <typename Stream, typename T>
requires (is_ostream_v<Stream> && !is_custom_v<T> && !is_empty_v<T> && !is_trivial_v<T> && is_struct_v<T>)
void write(Stream & s, T const & x)
{
write_struct(s, x);
}
template <typename Stream, typename T>
requires (is_istream_v<Stream> && !is_custom_v<T> && !is_empty_v<T> && !is_trivial_v<T> && is_struct_v<T>)
void read(Stream & s, T & x)
{
read_struct(s, x);
}
}

View file

@ -0,0 +1,124 @@
#pragma once
#include <psemek/sir/platform.hpp>
#include <psemek/sir/stream.hpp>
#include <type_traits>
namespace psemek::sir
{
namespace detail
{
struct fake_istream
{
void read(char *, std::size_t){}
std::size_t offset() const { return 0; }
};
struct fake_ostream
{
void write(char const *, std::size_t){}
std::size_t offset() const { return 0; }
};
static_assert(is_istream_v<fake_istream>);
static_assert(is_ostream_v<fake_ostream>);
template <typename T>
constexpr bool is_custom_helper = requires (T & x, fake_istream & is, fake_ostream & os)
{
write(os, x);
read(is, x);
};
}
template <typename T>
struct is_custom
: std::bool_constant<detail::is_custom_helper<T>>
{};
template <typename T>
constexpr bool is_custom_v = is_custom<T>::value;
template <typename T>
struct is_empty
: std::bool_constant<std::is_empty_v<T>>
{};
template <typename T>
constexpr bool is_empty_v = is_empty<T>::value;
template <typename T>
struct is_trivial
: std::bool_constant<std::is_trivial_v<T> && std::is_standard_layout_v<T>>
{};
template <typename T>
constexpr bool is_trivial_v = is_trivial<T>::value;
template <typename Stream, typename T>
requires (is_ostream_v<Stream> && !is_custom_v<T> && is_empty_v<T>)
void write(Stream &, T const &)
{}
template <typename Stream, typename T>
requires (is_istream_v<Stream> && !is_custom_v<T> && is_empty_v<T>)
void read(Stream &, T &)
{}
template <typename T, typename Stream>
requires is_ostream_v<Stream>
void write_padding(Stream & s)
{
char const zeros[alignof(T)] = {};
std::size_t const padding = (alignof(T) - s.offset()) % alignof(T);
if (padding != 0)
s.write(zeros, padding);
}
template <typename T, typename Stream>
requires is_istream_v<Stream>
void read_padding(Stream & s)
{
char zeros[alignof(T)] = {};
std::size_t const padding = (alignof(T) - s.offset()) % alignof(T);
if (padding != 0)
s.read(zeros, padding);
}
template <typename Stream, typename T>
requires (is_ostream_v<Stream> && !is_custom_v<T> && !is_empty_v<T> && is_trivial_v<T>)
void write(Stream & s, T const & x)
{
write_padding<T>(s);
s.write(reinterpret_cast<char const *>(std::addressof(x)), sizeof(x));
}
template <typename Stream, typename T>
requires (is_istream_v<Stream> && !is_custom_v<T> && !is_empty_v<T> && is_trivial_v<T>)
void read(Stream & s, T & x)
{
read_padding<T>(s);
s.read(reinterpret_cast<char *>(std::addressof(x)), sizeof(x));
}
template <typename Iterator>
void write_sequence(ostream & s, Iterator begin, Iterator end)
{
for (; begin != end; ++begin)
write(s, *begin);
}
template <typename Iterator>
void read_sequence(istream & s, Iterator begin, Iterator end)
{
for (; begin != end; ++begin)
read(s, *begin);
}
}

View file

@ -0,0 +1,180 @@
#include <psemek/test/test.hpp>
#include <psemek/sir/container.hpp>
#include <psemek/io/memory_stream.hpp>
#include "test_type.hpp"
#include <array>
#include <vector>
#include <list>
#include <deque>
#include <set>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <string>
using namespace psemek;
namespace
{
template <typename Container>
void test_container(Container const & c, bool ordered = true)
{
using std::begin;
using std::end;
using std::cbegin;
using std::cend;
using std::size;
char buffer[1024];
{
io::memory_ostream s{buffer, buffer + size(buffer)};
sir::ostream os{s};
write(os, c);
}
Container c2;
{
io::memory_istream s{buffer, buffer + size(buffer)};
sir::istream is{s};
read(is, c2);
}
expect_equal(std::size(c), std::size(c2));
if (ordered)
{
for (auto i1 = cbegin(c), i2 = cbegin(c2); i1 != cend(c); ++i1, ++i2)
expect_equal(*i1, *i2);
}
else
{
for (auto i1 = begin(c); i1 != end(c); ++i1)
expect(std::find(begin(c2), end(c2), *i1) != end(c2));
for (auto i2 = begin(c2); i2 != end(c2); ++i2)
expect(std::find(begin(c), end(c), *i2) != end(c));
}
}
}
test_case(sir_container_c__array_trivial)
{
std::uint32_t c[13] { 15, 16, 18, 2, 65535, 14, 5, 8, 42, 555, 18, 729, 76 };
test_container(c);
}
test_case(sir_container_c__array_custom)
{
test_type c[3] { {15u}, {65535u}, {1234567u} };
test_container(c);
}
test_case(sir_container_std__array_trivial)
{
std::array<std::uint32_t, 13> c { 15, 16, 18, 2, 65535, 14, 5, 8, 42, 555, 18, 729, 76 };
test_container(c);
}
test_case(sir_container_std__array_custom)
{
std::array<test_type, 3> c {{ {15u}, {65535u}, {1234567u} }};
test_container(c);
}
test_case(sir_container_vector_trivial)
{
std::vector<std::uint32_t> c { 15, 16, 18, 2, 65535, 14, 5, 8, 42, 555, 18, 729, 76 };
test_container(c);
}
test_case(sir_container_vector_custom)
{
std::vector<test_type> c { {15u}, {65535u}, {1234567u} };
test_container(c);
}
test_case(sir_container_list_trivial)
{
std::list<std::uint32_t> c { 15, 16, 18, 2, 65535, 14, 5, 8, 42, 555, 18, 729, 76 };
test_container(c);
}
test_case(sir_container_list_custom)
{
std::list<test_type> c { {15u}, {65535u}, {1234567u} };
test_container(c);
}
test_case(sir_container_deque_trivial)
{
std::deque<std::uint32_t> c { 15, 16, 18, 2, 65535, 14, 5, 8, 42, 555, 18, 729, 76 };
test_container(c);
}
test_case(sir_container_deque_custom)
{
std::deque<test_type> c { {15u}, {65535u}, {1234567u} };
test_container(c);
}
test_case(sir_container_set_trivial)
{
std::set<std::uint32_t> c { 15, 16, 18, 2, 65535, 14, 5, 8, 42, 555, 18, 729, 76 };
test_container(c);
}
test_case(sir_container_set_custom)
{
std::set<test_type> c { {15u}, {65535u}, {1234567u} };
test_container(c);
}
test_case(sir_container_unordered__set_trivial)
{
std::unordered_set<std::uint32_t> c { 15, 16, 18, 2, 65535, 14, 5, 8, 42, 555, 18, 729, 76 };
test_container(c, false);
}
test_case(sir_container_unordered__set_custom)
{
std::unordered_set<test_type> c { {15u}, {65535u}, {1234567u} };
test_container(c, false);
}
test_case(sir_container_map_trivial)
{
std::map<std::uint32_t, float> c { {10, 3.1415f}, {15, 6.7123f}, {1, -24.444f} };
test_container(c);
}
test_case(sir_container_map_custom__key)
{
std::map<test_type, float> c { {{15u}, 3.1415f}, {{65535u}, 23.451f}, {{1234567u}, -29.231f} };
test_container(c);
}
test_case(sir_container_unordered__map_trivial)
{
std::unordered_map<std::uint32_t, float> c { {10, 3.1415f}, {15, 6.7123f}, {1, -24.444f} };
test_container(c, false);
}
test_case(sir_container_unordered__map_custom__key)
{
std::unordered_map<test_type, float> c { {{15u}, 3.1415f}, {{65535u}, 23.451f}, {{1234567u}, -29.231f} };
test_container(c, false);
}
test_case(sir_container_string)
{
std::string c = "Hello, world!";
test_container(c);
}

32
libs/sir/tests/custom.cpp Normal file
View file

@ -0,0 +1,32 @@
#include <psemek/test/test.hpp>
#include <psemek/sir/container.hpp>
#include <psemek/io/memory_stream.hpp>
#include "test_type.hpp"
using namespace psemek;
test_case(sir_custom)
{
char buffer[1024];
test_type x;
x.value = 42;
{
io::memory_ostream s{buffer, buffer + std::size(buffer)};
sir::ostream os{s};
write(os, x);
}
test_type x2;
{
io::memory_istream s{buffer, buffer + std::size(buffer)};
sir::istream is{s};
read(is, x2);
}
expect_equal(x, x2);
}

83
libs/sir/tests/memory.cpp Normal file
View file

@ -0,0 +1,83 @@
#include <psemek/test/test.hpp>
#include <psemek/sir/memory.hpp>
#include <vector>
#include <set>
#include <map>
using namespace psemek;
namespace
{
template <typename C1, typename C2>
void read_write(C1 const & c1, C2 & c2)
{
using std::size;
char buffer[1024];
{
io::memory_ostream s{buffer, buffer + size(buffer)};
sir::ostream os{s};
write(os, c1);
}
{
io::memory_istream s{buffer, buffer + size(buffer)};
sir::memory_istream is{s};
read(is, c2);
}
}
}
test_case(sir_memory_vector)
{
std::vector<std::uint32_t> c1{ 15, 16, 18, 2, 65535, 14, 5, 8, 42, 555, 18, 729, 76 };
sir::vector<std::uint32_t> c2;
read_write(c1, c2);
expect_equal(c1.size(), c2.size());
for (std::size_t i = 0; i < c1.size(); ++i)
expect_equal(c1[i], c2[i]);
}
test_case(sir_memory_set)
{
std::set<std::uint32_t> c1{ 15, 16, 18, 2, 65535, 14, 5, 8, 42, 555, 18, 729, 76 };
sir::set<std::uint32_t> c2;
read_write(c1, c2);
expect_equal(c1.size(), c2.size());
expect(std::is_sorted(c2.begin(), c2.end()));
auto i1 = c1.begin();
auto i2 = c2.begin();
for (; i1 != c1.end(); ++i1, ++i2)
expect_equal(*i1, *i2);
}
test_case(sir_memory_map)
{
std::map<std::uint32_t, float> c1 { {10, 3.1415f}, {15, 6.7123f}, {1, -24.444f} };
sir::map<std::uint32_t, float> c2;
read_write(c1, c2);
expect_equal(c1.size(), c2.size());
auto i1 = c1.begin();
auto i2 = c2.begin();
for (; i1 != c1.end(); ++i1, ++i2)
{
expect_equal(i1->first, i2->first);
expect_equal(i1->second, i2->second);
}
}

62
libs/sir/tests/struct.cpp Normal file
View file

@ -0,0 +1,62 @@
#include <psemek/test/test.hpp>
#include <psemek/sir/struct.hpp>
#include <psemek/io/memory_stream.hpp>
#include "test_type.hpp"
#include <utility>
#include <tuple>
using namespace psemek;
namespace
{
template <typename T>
void test_value(T const & x)
{
char buffer[1024];
{
io::memory_ostream s{buffer, buffer + std::size(buffer)};
sir::ostream os{s};
write(os, x);
}
T x2;
{
io::memory_istream s{buffer, buffer + std::size(buffer)};
sir::istream is{s};
read(is, x2);
}
expect_equal(x, x2);
}
}
test_case(sir_struct_pair_trivial)
{
std::pair<char, std::uint32_t> p{'a', 1000000u};
test_value(p);
}
test_case(sir_struct_pair_custom)
{
std::pair<char, test_type> p{'a', {1000000u}};
test_value(p);
}
test_case(sir_struct_tuple_trivial)
{
std::tuple<char, std::uint32_t, float> p{'a', 1000000u, 3.1415f};
test_value(p);
}
test_case(sir_struct_tuple_custom)
{
std::tuple<char, test_type, float> p{'a', {1000000u}, 3.1415f};
test_value(p);
}

View file

@ -0,0 +1,49 @@
#pragma once
#include <psemek/test/test.hpp>
#include <psemek/sir/container.hpp>
#include <iostream>
#include <functional>
using namespace psemek;
struct test_type
{
std::uint32_t value;
friend auto operator <=> (test_type const &, test_type const &) = default;
};
template <typename Stream>
void write(Stream & s, test_type const & x)
{
write(s, x.value);
}
template <typename Stream>
void read(Stream & s, test_type & x)
{
read(s, x.value);
}
static_assert(sir::is_custom_v<test_type>);
inline std::ostream & operator << (std::ostream & s, test_type const & x)
{
return s << '{' << x.value << '}';
}
namespace std
{
template <>
struct hash<test_type>
{
std::size_t operator()(test_type const & x) const
{
return x.value;
}
};
}

161
libs/sir/tests/trivial.cpp Normal file
View file

@ -0,0 +1,161 @@
#include <psemek/test/test.hpp>
#include <psemek/sir/trivial.hpp>
#include <psemek/io/memory_stream.hpp>
#include <numeric>
#include <array>
using namespace psemek;
namespace
{
template <typename T>
void test_primitive_write(T value)
{
T result;
io::memory_ostream s{reinterpret_cast<char *>(&result), reinterpret_cast<char *>(&result) + sizeof(result)};
sir::ostream os{s};
sir::write(os, value);
expect_equal(result, value);
}
template <typename T>
void test_primitive_read(T value)
{
T result;
io::memory_istream s{reinterpret_cast<char *>(&value), reinterpret_cast<char *>(&value) + sizeof(value)};
sir::istream is{s};
sir::read(is, result);
expect_equal(result, value);
}
struct trivial_type
{
char c;
std::int64_t i;
std::uint32_t u;
float f;
friend bool operator == (trivial_type const &, trivial_type const &) = default;
friend std::ostream & operator << (std::ostream & os, trivial_type const &)
{
return os << "trivial_type{...}";
}
};
}
test_case(sir_primitive_write)
{
test_primitive_write<char>('a');
test_primitive_write<std::uint8_t>(42);
test_primitive_write<std::uint16_t>(42042);
test_primitive_write<std::uint32_t>(1000000000u);
test_primitive_write<std::uint64_t>(1000000000ull);
test_primitive_write<std::int8_t>(-42);
test_primitive_write<std::int16_t>(-10042);
test_primitive_write<std::int32_t>(-1000000000u);
test_primitive_write<std::int64_t>(-1000000000ull);
test_primitive_write<float>(3.14159265f);
test_primitive_write<double>(2.718281828459045);
}
test_case(sir_primitive_read)
{
test_primitive_read<char>('a');
test_primitive_read<std::uint8_t>(42);
test_primitive_read<std::uint16_t>(42042);
test_primitive_read<std::uint32_t>(1000000000u);
test_primitive_read<std::uint64_t>(1000000000ull);
test_primitive_read<std::int8_t>(-42);
test_primitive_read<std::int16_t>(-10042);
test_primitive_read<std::int32_t>(-1000000000u);
test_primitive_read<std::int64_t>(-1000000000ull);
test_primitive_read<float>(3.14159265f);
test_primitive_read<double>(2.718281828459045);
}
test_case(sir_padding_write)
{
char buffer[256];
io::memory_ostream s{buffer, buffer + std::size(buffer)};
sir::ostream os{s};
sir::write(os, char('a'));
expect_equal(os.offset(), 1);
sir::write(os, std::uint32_t(42));
expect_equal(os.offset(), 8);
sir::write(os, char('b'));
expect_equal(os.offset(), 9);
sir::write(os, std::int32_t(-42));
expect_equal(os.offset(), 16);
}
test_case(sir_padding_read)
{
char buffer[256] = {0};
io::memory_istream s{buffer, buffer + std::size(buffer)};
sir::istream is{s};
char c;
std::uint32_t ui;
sir::read(is, c);
expect_equal(is.offset(), 1);
sir::read(is, ui);
expect_equal(is.offset(), 8);
sir::read(is, c);
expect_equal(is.offset(), 9);
sir::read(is, ui);
expect_equal(is.offset(), 16);
}
test_case(sir_trivial)
{
char buffer[256];
trivial_type value{'a', 42, 12345, 3.1415f};
{
io::memory_ostream s{buffer, buffer + std::size(buffer)};
sir::ostream os{s};
sir::write(os, char('x'));
sir::write(os, value);
expect_equal(os.offset(), sizeof(trivial_type) + alignof(trivial_type));
}
{
io::memory_istream s{buffer, buffer + std::size(buffer)};
sir::istream is{s};
char c;
trivial_type read_value;
sir::read(is, c);
sir::read(is, read_value);
expect_equal(c, 'x');
expect_equal(is.offset(), sizeof(trivial_type) + alignof(trivial_type));
expect_equal(read_value, value);
}
}
test_case(sir_trivial__array)
{
char buffer[256];
{
std::array<std::uint32_t, 7> array;
std::iota(std::begin(array), std::end(array), 0);
io::memory_ostream s{buffer, buffer + std::size(buffer)};
sir::ostream os{s};
sir::write(os, array);
expect_equal(os.offset(), std::size(array) * sizeof(array[0]));
}
}