From d4f299f277e335307d1862df59fa33f9730c0c69 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Mon, 13 Jun 2022 00:17:58 +0300 Subject: [PATCH] Add basic binary serialization library with tests --- libs/sir/CMakeLists.txt | 8 + libs/sir/include/psemek/sir/container.hpp | 197 +++++++++++++++ libs/sir/include/psemek/sir/memory.hpp | 295 ++++++++++++++++++++++ libs/sir/include/psemek/sir/platform.hpp | 32 +++ libs/sir/include/psemek/sir/stream.hpp | 128 ++++++++++ libs/sir/include/psemek/sir/struct.hpp | 89 +++++++ libs/sir/include/psemek/sir/trivial.hpp | 124 +++++++++ libs/sir/tests/container.cpp | 180 +++++++++++++ libs/sir/tests/custom.cpp | 32 +++ libs/sir/tests/memory.cpp | 83 ++++++ libs/sir/tests/struct.cpp | 62 +++++ libs/sir/tests/test_type.hpp | 49 ++++ libs/sir/tests/trivial.cpp | 161 ++++++++++++ 13 files changed, 1440 insertions(+) create mode 100644 libs/sir/CMakeLists.txt create mode 100644 libs/sir/include/psemek/sir/container.hpp create mode 100644 libs/sir/include/psemek/sir/memory.hpp create mode 100644 libs/sir/include/psemek/sir/platform.hpp create mode 100644 libs/sir/include/psemek/sir/stream.hpp create mode 100644 libs/sir/include/psemek/sir/struct.hpp create mode 100644 libs/sir/include/psemek/sir/trivial.hpp create mode 100644 libs/sir/tests/container.cpp create mode 100644 libs/sir/tests/custom.cpp create mode 100644 libs/sir/tests/memory.cpp create mode 100644 libs/sir/tests/struct.cpp create mode 100644 libs/sir/tests/test_type.hpp create mode 100644 libs/sir/tests/trivial.cpp diff --git a/libs/sir/CMakeLists.txt b/libs/sir/CMakeLists.txt new file mode 100644 index 00000000..ca01f895 --- /dev/null +++ b/libs/sir/CMakeLists.txt @@ -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) diff --git a/libs/sir/include/psemek/sir/container.hpp b/libs/sir/include/psemek/sir/container.hpp new file mode 100644 index 00000000..399a7f1e --- /dev/null +++ b/libs/sir/include/psemek/sir/container.hpp @@ -0,0 +1,197 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace psemek::sir +{ + + namespace detail + { + + using std::begin; + using std::end; + using std::size; + using std::data; + + template + constexpr bool is_container_helper = !is_struct_v && requires (std::remove_cv_t c) + { + begin(c); + end(c); + size(c); + requires requires (typename Container::value_type const & x) { c.insert(end(c), x); }; + }; + + template + constexpr bool is_contiguous_container_helper = is_container_helper && requires (Container & c) + { + data(c); + }; + + template + constexpr bool is_associative_container_helper = is_container_helper && requires + { + typename Container::key_type; + typename Container::mapped_type; + }; + + } + + using size_type = std::uint32_t; + + template + struct is_container + : std::bool_constant> + {}; + + template + constexpr bool is_container_v = is_container::value; + + template + struct is_contiguous_container + : std::bool_constant> + {}; + + template + constexpr bool is_contiguous_container_v = is_contiguous_container::value; + + template + struct is_associative_container + : std::bool_constant> + {}; + + template + constexpr bool is_associative_container_v = is_associative_container::value; + + template + struct has_static_size + : std::false_type + {}; + + template + constexpr bool has_static_size_v = has_static_size::value; + + template + struct has_static_size + : std::true_type + {}; + + template + struct has_static_size> + : std::true_type + {}; + + template + requires is_ostream_v + void write_size(Stream & s, Container const & c) + { + write(s, static_cast(std::size(c))); + } + + template + requires is_istream_v + void read_size(Stream & s, Container & c) + { + size_type size; + read(s, size); + c.resize(size); + } + + template + requires (is_ostream_v && is_trivial_v) + void write_contiguous(Stream & s, T const * begin, std::size_t size) + { + write_padding(s); + s.write(reinterpret_cast(begin), sizeof(T) * size); + } + + template + requires (is_istream_v && is_trivial_v) + void read_contiguous(Stream & s, T * begin, std::size_t size) + { + read_padding(s); + s.read(reinterpret_cast(begin), sizeof(T) * size); + } + + template + requires is_ostream_v + void write_container(Stream & s, Container const & c) + { + if constexpr (!has_static_size_v) + write_size(s, c); + + using std::begin; + using std::end; + using std::data; + using std::size; + + if constexpr (is_contiguous_container_v && is_trivial_v>) + write_contiguous(s, data(c), size(c)); + else + write_sequence(s, begin(c), end(c)); + } + + template + requires is_istream_v + 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; + + if constexpr (has_static_size_v) + read_sequence(s, begin(c), end(c)); + else if constexpr (is_contiguous_container_v && is_trivial_v) + { + read_size(s, c); + read_contiguous(s, data(c), size(c)); + } + else if constexpr (is_associative_container_v) + { + 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 + requires (is_ostream_v && !is_custom_v && !is_empty_v && !is_trivial_v && !is_struct_v && is_container_v) + void write(Stream & s, T const & x) + { + write_container(s, x); + } + + template + requires (is_istream_v && !is_custom_v && !is_empty_v && !is_trivial_v && !is_struct_v && is_container_v) + void read(Stream & s, T & x) + { + read_container(s, x); + } + +} diff --git a/libs/sir/include/psemek/sir/memory.hpp b/libs/sir/include/psemek/sir/memory.hpp new file mode 100644 index 00000000..061d9ac4 --- /dev/null +++ b/libs/sir/include/psemek/sir/memory.hpp @@ -0,0 +1,295 @@ +#pragma once + +#include +#include +#include +#include + +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(stream()).data(); + } + + void advance(std::size_t n) + { + static_cast(stream()).advance(n); + } + + private: + std::string_view data_; + std::size_t offset_; + }; + + static_assert(is_istream_v); + + namespace detail + { + + template + 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 + requires is_trivial_v + void read(memory_istream & s, vector & x) + { + size_type size; + read(s, size); + read_padding(s); + auto begin = reinterpret_cast(s.data()); + s.advance(size * sizeof(T)); + auto end = reinterpret_cast(s.data()); + x = vector(begin, end); + } + + template > + 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 + 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 + requires is_trivial_v + void read(memory_istream & s, set & x) + { + size_type size; + read(s, size); + read_padding(s); + auto begin = reinterpret_cast(s.data()); + s.advance(size * sizeof(T)); + auto end = reinterpret_cast(s.data()); + x = set(begin, end); + } + + template > + 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 + 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 + requires is_trivial_v::pair> + void read(memory_istream & s, map & x) + { + using T = typename map::pair; + size_type size; + read(s, size); + read_padding(s); + auto begin = reinterpret_cast(s.data()); + s.advance(size * sizeof(T)); + auto end = reinterpret_cast(s.data()); + x = map(begin, end); + } + + } + + using detail::vector; + using detail::set; + using detail::map; + +} diff --git a/libs/sir/include/psemek/sir/platform.hpp b/libs/sir/include/psemek/sir/platform.hpp new file mode 100644 index 00000000..8b4678bb --- /dev/null +++ b/libs/sir/include/psemek/sir/platform.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +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); + +} diff --git a/libs/sir/include/psemek/sir/stream.hpp b/libs/sir/include/psemek/sir/stream.hpp new file mode 100644 index 00000000..28c8e226 --- /dev/null +++ b/libs/sir/include/psemek/sir/stream.hpp @@ -0,0 +1,128 @@ +#pragma once + +#include + +namespace psemek::sir +{ + + namespace detail + { + + template + 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; + }; + + template + 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; + }; + + } + + template + struct is_istream + : std::bool_constant> + {}; + + template + constexpr bool is_istream_v = is_istream::value; + + template + struct is_ostream + : std::bool_constant> + {}; + + template + constexpr bool is_ostream_v = is_ostream::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); + static_assert(is_ostream_v); + static_assert(is_istream_v); + static_assert(is_ostream_v); + +} diff --git a/libs/sir/include/psemek/sir/struct.hpp b/libs/sir/include/psemek/sir/struct.hpp new file mode 100644 index 00000000..b2cc4c03 --- /dev/null +++ b/libs/sir/include/psemek/sir/struct.hpp @@ -0,0 +1,89 @@ +#pragma once + +#include + +#include + +namespace psemek::sir +{ + + namespace detail + { + + template + constexpr bool is_struct_helper = requires (T x) + { + std::get<0>(x); + std::tuple_size_v; + }; + + template + void write_struct_impl(ostream & s, T const & x, std::index_sequence) + { + (write(s, std::get(x)), ...); + } + + template + void read_struct_impl(istream & s, T & x, std::index_sequence) + { + (read(s, std::get(x)), ...); + } + + } + + template + struct is_struct + : std::bool_constant> + {}; + + template + constexpr bool is_struct_v = is_struct::value; + + template + struct is_struct + : std::true_type + {}; + + template + requires is_ostream_v + void write_struct(Stream & s, T const & x) + { + detail::write_struct_impl(s, x, std::make_index_sequence>{}); + } + + template + requires is_istream_v + void read_struct(Stream & s, T & x) + { + detail::read_struct_impl(s, x, std::make_index_sequence>{}); + } + + template + requires is_ostream_v + void write_struct(ostream & s, T const (&x)[N]) + { + write_sequence(s, x, x + N); + } + + template + requires is_istream_v + void read_struct(Stream & s, T (&x)[N]) + { + read_sequence(s, x, x + N); + } + + template + requires (is_ostream_v && !is_custom_v && !is_empty_v && !is_trivial_v && is_struct_v) + void write(Stream & s, T const & x) + { + write_struct(s, x); + } + + template + requires (is_istream_v && !is_custom_v && !is_empty_v && !is_trivial_v && is_struct_v) + void read(Stream & s, T & x) + { + read_struct(s, x); + } + +} diff --git a/libs/sir/include/psemek/sir/trivial.hpp b/libs/sir/include/psemek/sir/trivial.hpp new file mode 100644 index 00000000..fbc80329 --- /dev/null +++ b/libs/sir/include/psemek/sir/trivial.hpp @@ -0,0 +1,124 @@ +#pragma once + +#include +#include + +#include + +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); + static_assert(is_ostream_v); + + template + constexpr bool is_custom_helper = requires (T & x, fake_istream & is, fake_ostream & os) + { + write(os, x); + read(is, x); + }; + + } + + template + struct is_custom + : std::bool_constant> + {}; + + template + constexpr bool is_custom_v = is_custom::value; + + template + struct is_empty + : std::bool_constant> + {}; + + template + constexpr bool is_empty_v = is_empty::value; + + template + struct is_trivial + : std::bool_constant && std::is_standard_layout_v> + {}; + + template + constexpr bool is_trivial_v = is_trivial::value; + + template + requires (is_ostream_v && !is_custom_v && is_empty_v) + void write(Stream &, T const &) + {} + + template + requires (is_istream_v && !is_custom_v && is_empty_v) + void read(Stream &, T &) + {} + + template + requires is_ostream_v + 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 + requires is_istream_v + 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 + requires (is_ostream_v && !is_custom_v && !is_empty_v && is_trivial_v) + void write(Stream & s, T const & x) + { + write_padding(s); + s.write(reinterpret_cast(std::addressof(x)), sizeof(x)); + } + + template + requires (is_istream_v && !is_custom_v && !is_empty_v && is_trivial_v) + void read(Stream & s, T & x) + { + read_padding(s); + s.read(reinterpret_cast(std::addressof(x)), sizeof(x)); + } + + template + void write_sequence(ostream & s, Iterator begin, Iterator end) + { + for (; begin != end; ++begin) + write(s, *begin); + } + + template + void read_sequence(istream & s, Iterator begin, Iterator end) + { + for (; begin != end; ++begin) + read(s, *begin); + } + +} diff --git a/libs/sir/tests/container.cpp b/libs/sir/tests/container.cpp new file mode 100644 index 00000000..65cff5c0 --- /dev/null +++ b/libs/sir/tests/container.cpp @@ -0,0 +1,180 @@ +#include + +#include +#include + +#include "test_type.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace psemek; + +namespace +{ + + template + 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 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 c {{ {15u}, {65535u}, {1234567u} }}; + test_container(c); +} + +test_case(sir_container_vector_trivial) +{ + std::vector 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 c { {15u}, {65535u}, {1234567u} }; + test_container(c); +} + +test_case(sir_container_list_trivial) +{ + std::list 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 c { {15u}, {65535u}, {1234567u} }; + test_container(c); +} + +test_case(sir_container_deque_trivial) +{ + std::deque 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 c { {15u}, {65535u}, {1234567u} }; + test_container(c); +} + +test_case(sir_container_set_trivial) +{ + std::set 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 c { {15u}, {65535u}, {1234567u} }; + test_container(c); +} + +test_case(sir_container_unordered__set_trivial) +{ + std::unordered_set 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 c { {15u}, {65535u}, {1234567u} }; + test_container(c, false); +} + +test_case(sir_container_map_trivial) +{ + std::map c { {10, 3.1415f}, {15, 6.7123f}, {1, -24.444f} }; + test_container(c); +} + +test_case(sir_container_map_custom__key) +{ + std::map c { {{15u}, 3.1415f}, {{65535u}, 23.451f}, {{1234567u}, -29.231f} }; + test_container(c); +} + +test_case(sir_container_unordered__map_trivial) +{ + std::unordered_map 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 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); +} diff --git a/libs/sir/tests/custom.cpp b/libs/sir/tests/custom.cpp new file mode 100644 index 00000000..8a25f65d --- /dev/null +++ b/libs/sir/tests/custom.cpp @@ -0,0 +1,32 @@ +#include + +#include +#include + +#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); +} diff --git a/libs/sir/tests/memory.cpp b/libs/sir/tests/memory.cpp new file mode 100644 index 00000000..82f52486 --- /dev/null +++ b/libs/sir/tests/memory.cpp @@ -0,0 +1,83 @@ +#include + +#include + +#include +#include +#include + +using namespace psemek; + +namespace +{ + + template + 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 c1{ 15, 16, 18, 2, 65535, 14, 5, 8, 42, 555, 18, 729, 76 }; + sir::vector 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 c1{ 15, 16, 18, 2, 65535, 14, 5, 8, 42, 555, 18, 729, 76 }; + sir::set 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 c1 { {10, 3.1415f}, {15, 6.7123f}, {1, -24.444f} }; + sir::map 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); + } +} diff --git a/libs/sir/tests/struct.cpp b/libs/sir/tests/struct.cpp new file mode 100644 index 00000000..5342886e --- /dev/null +++ b/libs/sir/tests/struct.cpp @@ -0,0 +1,62 @@ +#include + +#include +#include + +#include "test_type.hpp" + +#include +#include + +using namespace psemek; + +namespace +{ + + template + 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 p{'a', 1000000u}; + test_value(p); +} + +test_case(sir_struct_pair_custom) +{ + std::pair p{'a', {1000000u}}; + test_value(p); +} + +test_case(sir_struct_tuple_trivial) +{ + std::tuple p{'a', 1000000u, 3.1415f}; + test_value(p); +} + +test_case(sir_struct_tuple_custom) +{ + std::tuple p{'a', {1000000u}, 3.1415f}; + test_value(p); +} diff --git a/libs/sir/tests/test_type.hpp b/libs/sir/tests/test_type.hpp new file mode 100644 index 00000000..672df883 --- /dev/null +++ b/libs/sir/tests/test_type.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +#include +#include + +using namespace psemek; + +struct test_type +{ + std::uint32_t value; + + friend auto operator <=> (test_type const &, test_type const &) = default; +}; + +template +void write(Stream & s, test_type const & x) +{ + write(s, x.value); +} + +template +void read(Stream & s, test_type & x) +{ + read(s, x.value); +} + +static_assert(sir::is_custom_v); + +inline std::ostream & operator << (std::ostream & s, test_type const & x) +{ + return s << '{' << x.value << '}'; +} + +namespace std +{ + + template <> + struct hash + { + std::size_t operator()(test_type const & x) const + { + return x.value; + } + }; + +} diff --git a/libs/sir/tests/trivial.cpp b/libs/sir/tests/trivial.cpp new file mode 100644 index 00000000..3fb2ae75 --- /dev/null +++ b/libs/sir/tests/trivial.cpp @@ -0,0 +1,161 @@ +#include + +#include +#include + +#include +#include + +using namespace psemek; + +namespace +{ + + template + void test_primitive_write(T value) + { + T result; + io::memory_ostream s{reinterpret_cast(&result), reinterpret_cast(&result) + sizeof(result)}; + sir::ostream os{s}; + sir::write(os, value); + + expect_equal(result, value); + } + + template + void test_primitive_read(T value) + { + T result; + io::memory_istream s{reinterpret_cast(&value), reinterpret_cast(&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('a'); + test_primitive_write(42); + test_primitive_write(42042); + test_primitive_write(1000000000u); + test_primitive_write(1000000000ull); + test_primitive_write(-42); + test_primitive_write(-10042); + test_primitive_write(-1000000000u); + test_primitive_write(-1000000000ull); + test_primitive_write(3.14159265f); + test_primitive_write(2.718281828459045); +} + +test_case(sir_primitive_read) +{ + test_primitive_read('a'); + test_primitive_read(42); + test_primitive_read(42042); + test_primitive_read(1000000000u); + test_primitive_read(1000000000ull); + test_primitive_read(-42); + test_primitive_read(-10042); + test_primitive_read(-1000000000u); + test_primitive_read(-1000000000ull); + test_primitive_read(3.14159265f); + test_primitive_read(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 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])); + } +}