From 3ae51738b860606a639fcf6891259b566bd14f86 Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Mon, 22 Jul 2024 22:01:24 -0700 Subject: [PATCH] Split up decoding tests --- test/decode/decode.hpp | 138 ++++++ test/decode/test_decode.cpp | 114 +++++ test/decode/test_decode_errors.cpp | 67 +++ test/decode/test_decode_integers.cpp | 68 +++ test/decode/test_decode_some.cpp | 190 ++++++++ test/matchers.hpp | 87 ++++ test/test_decode.cpp | 624 --------------------------- 7 files changed, 664 insertions(+), 624 deletions(-) create mode 100644 test/decode/decode.hpp create mode 100644 test/decode/test_decode.cpp create mode 100644 test/decode/test_decode_errors.cpp create mode 100644 test/decode/test_decode_integers.cpp create mode 100644 test/decode/test_decode_some.cpp create mode 100644 test/matchers.hpp delete mode 100644 test/test_decode.cpp diff --git a/test/decode/decode.hpp b/test/decode/decode.hpp new file mode 100644 index 0000000..5d67efa --- /dev/null +++ b/test/decode/decode.hpp @@ -0,0 +1,138 @@ +#ifndef INC_BENCODE_TEST_DECODE_DECODE_HPP +#define INC_BENCODE_TEST_DECODE_DECODE_HPP + +#include +#include +#include + +#include "bencode.hpp" + +using std::istringstream; +#if __cpp_char8_t +using u8istringstream = std::basic_istringstream; +#endif + +template +struct char_type { + using type = typename T::value_type; +}; + +template +struct char_type { using type = std::remove_cv_t; }; +template +struct char_type> { using type = Char; }; + +template +inline constexpr bool supports_view = + !std::is_same_v::type, std::byte>; +template +inline constexpr bool supports_view> = false; + +template +T make_data(const char *data) { + using Char = typename char_type::type; + if constexpr(std::is_constructible_v) + return T((Char*)(data)); + else + return T((Char*)data, (Char*)(data + std::strlen(data))); +} + +template +auto mix_decoder(DecodeArgs &&decode_args, DoDecode &&do_decode) { + return [decode_args, do_decode](auto &&data) { + return std::apply( + do_decode, decode_args(std::forward(data)) + ); + }; +} + +auto decode_args = [](auto &&data) { + return std::tuple(data); +}; +auto decode_iter_args = [](auto &&data) { + return std::make_tuple(data.begin(), data.end()); +}; +auto decode_ptr_len_args = [](auto &&data) { + return std::tuple( + data, bencode::detail::any_strlen(data) + ); +}; + +template +auto decode_tests(Builder &_, Callable &&decode) { + using boost::get; + using std::get; + + using OutType = decltype(decode(std::declval())); + using Integer = typename OutType::integer; + using String = typename OutType::string; + using List = typename OutType::list; + using Dict = typename OutType::dict; + auto S = &make_data; + + _.test("integer", [decode]() { + auto pos = make_data("i42e"); + auto pos_value = decode(pos); + expect(get(pos_value), equal_to(42)); + + auto neg = make_data("i-42e"); + auto neg_value = decode(neg); + expect(get(neg_value), equal_to(-42)); + }); + + _.test("string", [decode, S]() { + auto data = make_data("4:spam"); + // Silence GCC < 10. + [[maybe_unused]] auto within_data_memory = within_memory(data); + + auto value = decode(data); + auto str = get(value); + expect(str, equal_to(S("spam"))); + if constexpr(bencode::detail::is_view) { + expect(&*str.begin(), within_data_memory); + expect(&*str.end(), within_data_memory); + } + }); + + _.test("list", [decode]() { + auto data = make_data("li42ee"); + auto value = decode(data); + auto list = get(value); + expect(get(list[0]), equal_to(42)); + }); + + _.test("dict", [decode, S]() { + auto data = make_data("d4:spami42ee"); + // Silence GCC < 10. + [[maybe_unused]] auto within_data_memory = within_memory(data); + + auto value = decode(data); + auto dict = get(value); + expect(get(dict[S("spam")]), equal_to(42)); + + auto str = dict.find(S("spam"))->first; + expect(str, equal_to(S("spam"))); + if constexpr(bencode::detail::is_view) { + expect(&*str.begin(), within_data_memory); + expect(&*str.end(), within_data_memory); + } + }); + + _.test("nested", [decode, S]() { + auto data = make_data( + "d" + "3:one" "i1e" + "5:three" "l" "d" "3:bar" "i0e" "3:foo" "i0e" "e" "e" + "3:two" "l" "i3e" "3:foo" "i4e" "e" + "e" + ); + auto value = decode(data); + auto dict = get(value); + expect(get(dict[S("one")]), equal_to(1)); + expect(get(get(dict[S("two")])[1]), equal_to(S("foo"))); + expect(get(get(get(dict[S("three")])[0])[S("foo")]), + equal_to(0)); + }); +} + +#endif diff --git a/test/decode/test_decode.cpp b/test/decode/test_decode.cpp new file mode 100644 index 0000000..0e05960 --- /dev/null +++ b/test/decode/test_decode.cpp @@ -0,0 +1,114 @@ +#include +using namespace mettle; + +#include "bencode.hpp" + +#include "../matchers.hpp" +#include "decode.hpp" + +template +struct decode_suites { + decode_suites(DecodeArgs decode_args) + : decode_args(std::move(decode_args)) {} + DecodeArgs decode_args; + + template + void operator ()(Builder &_) const { + using InType = fixture_type_t; + using Char = typename char_type::type; + auto &decode_args = this->decode_args; + + subsuite<>(_, "decode", [decode_args](auto &_) { + decode_tests(_, mix_decoder(decode_args, + [](auto &&data, auto &&...rest) { + auto value = bencode::decode(data, rest...); + if constexpr(std::is_base_of_v) + expect(data, at_eof(data)); + return value; + })); + }); + + if constexpr(supports_view) { + subsuite<>(_, "decode_view", [decode_args](auto &_) { + decode_tests(_, mix_decoder(decode_args, + [](auto &&data, auto &&...rest) { + return bencode::decode_view(data, rest...); + })); + }); + } + + subsuite<>(_, "boost_decode", [decode_args](auto &_) { + decode_tests(_, mix_decoder(decode_args, + [](auto &&data, auto &&...rest) { + auto value = bencode::boost_decode(data, rest...); + if constexpr(std::is_base_of_v) + expect(data, at_eof(data)); + return value; + })); + }); + + if constexpr(supports_view) { + subsuite<>(_, "boost_decode_view", [decode_args](auto &_) { + decode_tests(_, mix_decoder(decode_args, + [](auto &&data, auto &&...rest) { + return bencode::boost_decode_view(data, rest...); + })); + }); + } + + subsuite< + bencode::data_for_char_t, bencode::boost_data_for_char_t + >(_, "basic_decode to", type_only, [decode_args](auto &_) { + using OutType = fixture_type_t; + decode_tests(_, mix_decoder(decode_args, + [](auto &&data, auto &&...rest) { + auto value = bencode::basic_decode(data, rest...); + if constexpr(std::is_base_of_v) + expect(data, at_eof(data)); + return value; + })); + }); + + if constexpr(supports_view) { + subsuite< + bencode::data_view_for_char_t, + bencode::boost_data_view_for_char_t + >(_, "basic_decode to", type_only, [decode_args](auto &_) { + using OutType = fixture_type_t; + decode_tests(_, mix_decoder(decode_args, + [](auto &&data, auto &&...rest) { + auto value = bencode::basic_decode(data, rest...); + if constexpr(std::is_base_of_v) + expect(data, at_eof(data)); + return value; + })); + }); + } + } +}; + +suite<> test_decode("decode", [](auto &_) { + subsuite< + const char *, std::string, std::vector, istringstream, +#if __cpp_char8_t + const char8_t *, std::u8string, std::vector, u8istringstream, +#endif + const std::byte *, std::vector + >(_, "decode", type_only, decode_suites(decode_args)); + + subsuite< + std::string, std::vector, +#if __cpp_char8_t + std::u8string, std::vector, +#endif + std::vector + >(_, "decode iterator pair", type_only, decode_suites(decode_iter_args)); + + subsuite< + const char *, +#if __cpp_char8_t + const char8_t *, +#endif + const std::byte * + >(_, "decode pointer/length", type_only, decode_suites(decode_ptr_len_args)); +}); diff --git a/test/decode/test_decode_errors.cpp b/test/decode/test_decode_errors.cpp new file mode 100644 index 0000000..4522bfd --- /dev/null +++ b/test/decode/test_decode_errors.cpp @@ -0,0 +1,67 @@ +#include +using namespace mettle; + +#include "bencode.hpp" + +#include "../matchers.hpp" + +suite<> test_decode_errors("decode error handling", [](auto &_) { + _.test("unexpected type token", []() { + expect([]() { bencode::decode("x"); }, + decode_error("unexpected type token", 0)); + }); + + _.test("unexpected end of input", []() { + auto eos = [](std::size_t offset) { + return decode_error( + "unexpected end of input", offset + ); + }; + + expect([]() { bencode::decode(""); }, eos(0)); + expect([]() { bencode::decode("i123"); }, eos(4)); + expect([]() { bencode::decode("3"); }, eos(1)); + expect([]() { bencode::decode("3:as"); }, eos(4)); + expect([]() { bencode::decode("l"); }, eos(1)); + expect([]() { bencode::decode("li1e"); }, eos(4)); + expect([]() { bencode::decode("d"); }, eos(1)); + expect([]() { bencode::decode("d1:a"); }, eos(4)); + expect([]() { bencode::decode("d1:ai1e"); }, eos(7)); + }); + + _.test("extraneous character", []() { + expect([]() { bencode::decode("i123ei"); }, + decode_error("extraneous character", 5)); + }); + + _.test("expected 'e' token", []() { + expect([]() { bencode::decode("i123i"); }, + decode_error("expected 'e' token", 4)); + }); + + _.test("unexpected 'e' token", []() { + expect([]() { bencode::decode("e"); }, + decode_error("unexpected 'e' token", 0)); + }); + + _.test("expected ':' token", []() { + expect([]() { bencode::decode("1abc"); }, + decode_error("expected ':' token", 1)); + }); + + _.test("expected string start token", []() { + expect( + []() { bencode::decode("di123ee"); }, + decode_error( + "expected string start token for dict key", 1 + ) + ); + }); + + _.test("duplicated key", []() { + expect([]() { bencode::decode("d3:fooi1e3:fooi1ee"); }, + decode_error( + "duplicated key in dict: \"foo\"", 17 + )); + }); +}); diff --git a/test/decode/test_decode_integers.cpp b/test/decode/test_decode_integers.cpp new file mode 100644 index 0000000..40d2a33 --- /dev/null +++ b/test/decode/test_decode_integers.cpp @@ -0,0 +1,68 @@ +#include +using namespace mettle; + +#include "bencode.hpp" + +#include "../matchers.hpp" + +suite<> test_decode_integer("decode integers", [](auto &_) { + using udata = bencode::basic_data< + std::variant, unsigned long long, std::string, std::vector, + bencode::map_proxy + >; + + _.test("max value", []() { + auto value = bencode::decode("i9223372036854775807e"); + expect(std::get(value), + equal_to(9223372036854775807LL)); + }); + + _.test("overflow", []() { + expect([]() { bencode::decode("i9223372036854775808e"); }, + decode_error("integer overflow", 20)); + expect([]() { bencode::decode("i9323372036854775807e"); }, + decode_error("integer overflow", 20)); + expect([]() { bencode::decode("i92233720368547758070e"); }, + decode_error("integer overflow", 20)); + }); + + _.test("min value", []() { + auto value = bencode::decode("i-9223372036854775808e"); + expect(std::get(value), + equal_to(-9223372036854775807LL - 1)); + }); + + _.test("underflow", []() { + expect([]() { bencode::decode("i-9223372036854775809e"); }, + decode_error("integer underflow", 21)); + expect([]() { bencode::decode("i-9323372036854775808e"); }, + decode_error("integer underflow", 21)); + expect([]() { bencode::decode("i-92233720368547758080e"); }, + decode_error("integer underflow", 21)); + }); + + _.test("max value (unsigned)", []() { + auto value = bencode::basic_decode("i18446744073709551615e"); + expect(std::get(value), + equal_to(18446744073709551615ULL)); + }); + + _.test("overflow (unsigned)", []() { + expect([]() { + bencode::basic_decode("i18446744073709551616e"); + }, decode_error("integer overflow", 21)); + expect([]() { + bencode::basic_decode("i19446744073709551615e"); + }, decode_error("integer overflow", 21)); + expect([]() { + bencode::basic_decode("i184467440737095516150e"); + }, decode_error("integer overflow", 21)); + }); + + _.test("negative value (unsigned)", []() { + expect( + []() { bencode::basic_decode("i-42e"); }, + decode_error("expected unsigned integer", 1) + ); + }); +}); diff --git a/test/decode/test_decode_some.cpp b/test/decode/test_decode_some.cpp new file mode 100644 index 0000000..a196482 --- /dev/null +++ b/test/decode/test_decode_some.cpp @@ -0,0 +1,190 @@ +#include +using namespace mettle; + +#include "bencode.hpp" + +#include "../matchers.hpp" +#include "decode.hpp" + +template +auto decode_some_tests(Builder &_, DecodeArgs &&decode_args, + DoDecode &&do_decode) { + using boost::get; + using std::get; + + _.test("successive objects", [decode_args, do_decode]() { + auto data = make_data("i42e4:goat"); + auto args = decode_args(data); + auto at_end = std::apply([](auto &&...t) { + return at_eof(std::forward(t)...); + }, args); + + auto first = std::apply(do_decode, args); + using OutType = decltype(first); + using Integer = typename OutType::integer; + using String = typename OutType::string; + auto S = &make_data; + + expect(get(first), equal_to(42)); + expect(std::get<0>(args), is_not(at_end)); + + auto second = std::apply(do_decode, args); + expect(get(second), equal_to(S("goat"))); + expect(std::get<0>(args), at_end); + }); +} + +template +struct decode_some_suites { + decode_some_suites(DecodeArgs decode_args) + : decode_args(std::move(decode_args)) {} + DecodeArgs decode_args; + + template + void operator ()(Builder &_) const { + using InType = fixture_type_t; + using Char = typename char_type::type; + auto &decode_args = this->decode_args; + + subsuite<>(_, "decode_some", [decode_args](auto &_) { + decode_tests( + _, mix_decoder(decode_args, [](auto &&data, auto &&...rest) { + auto at_end = at_eof(data, rest...); + auto value = bencode::decode_some(data, rest...); + expect(data, at_end); + return value; + }) + ); + decode_some_tests( + _, decode_args, [](auto &&data, auto &&...rest) { + return bencode::decode_some(data, rest...); + } + ); + }); + + if constexpr(supports_view) { + subsuite<>(_, "decode_view_some", [decode_args](auto &_) { + decode_tests( + _, mix_decoder(decode_args, [](auto &&data, auto &&...rest) { + auto at_end = at_eof(data, rest...); + auto value = bencode::decode_view_some(data, rest...); + expect(data, at_end); + return value; + }) + ); + decode_some_tests( + _, decode_args, [](auto &&data, auto &&...rest) { + return bencode::decode_view_some(data, rest...); + } + ); + }); + } + + subsuite<>(_, "boost_decode_some", [decode_args](auto &_) { + decode_tests( + _, mix_decoder(decode_args, [](auto &&data, auto &&...rest) { + auto at_end = at_eof(data, rest...); + auto value = bencode::boost_decode_some(data, rest...); + expect(data, at_end); + return value; + }) + ); + decode_some_tests( + _, decode_args, [](auto &&data, auto &&...rest) { + return bencode::boost_decode_some(data, rest...); + } + ); + }); + + if constexpr(supports_view) { + subsuite<>(_, "boost_decode_view_some", [decode_args](auto &_) { + decode_tests( + _, mix_decoder(decode_args, [](auto &&data, auto &&...rest) { + auto at_end = at_eof(data, rest...); + auto value = bencode::boost_decode_view_some(data, rest...); + expect(data, at_end); + return value; + }) + ); + decode_some_tests( + _, decode_args, [](auto &&data, auto &&...rest) { + return bencode::boost_decode_view_some(data, rest...); + } + ); + }); + } + + subsuite< + bencode::data_for_char_t, bencode::boost_data_for_char_t + >(_, "basic_decode_some to", type_only, [decode_args](auto &_) { + using OutType = fixture_type_t; + decode_tests( + _, mix_decoder(decode_args, [](auto &&data, auto &&...rest) { + auto at_end = at_eof(data, rest...); + auto value = bencode::basic_decode_some(data, rest...); + expect(data, at_end); + return value; + }) + ); + decode_some_tests( + _, decode_args, [](auto &&data, auto &&...rest) { + return bencode::basic_decode_some(data, rest...); + } + ); + }); + + if constexpr(supports_view) { + subsuite< + bencode::data_view_for_char_t, + bencode::boost_data_view_for_char_t + >(_, "basic_decode_some to", type_only, [decode_args](auto &_) { + using OutType = fixture_type_t; + decode_tests( + _, mix_decoder(decode_args, [](auto &&data, auto &&...rest) { + auto at_end = at_eof(data, rest...); + auto value = bencode::basic_decode_some(data, rest...); + expect(data, at_end); + return value; + }) + ); + decode_some_tests( + _, decode_args, [](auto &&data, auto &&...rest) { + return bencode::basic_decode_some(data, rest...); + } + ); + }); + } + } +}; + +suite<> test_decode_some("decode_some", [](auto &_) { + subsuite< + const char *, istringstream, +#if __cpp_char8_t + const char8_t *, u8istringstream, +#endif + const std::byte * + >(_, "decode_some", type_only, decode_some_suites(decode_args)); + + subsuite< + std::string, std::vector, +#if __cpp_char8_t + std::u8string, std::vector, +#endif + std::vector + >(_, "decode_some iterator pair", type_only, + decode_some_suites([](auto &&data) { + return std::make_tuple(data.begin(), data.end()); + }) + ); + + subsuite< + const char *, +#if __cpp_char8_t + const char8_t *, +#endif + const std::byte * + >(_, "decode_some pointer/length", type_only, + decode_some_suites(decode_ptr_len_args)); +}); diff --git a/test/matchers.hpp b/test/matchers.hpp new file mode 100644 index 0000000..153dcda --- /dev/null +++ b/test/matchers.hpp @@ -0,0 +1,87 @@ +#ifndef INC_BENCODE_TEST_MATCHERS_HPP +#define INC_BENCODE_TEST_MATCHERS_HPP + +#include +using namespace mettle; + +struct at_eof_base : matcher_tag { + std::string desc() const { + return "at eof"; + } +}; + +template +struct at_eof; + +template +struct at_eof : at_eof_base { + at_eof(const Char *) {} + bool operator ()(const Char *s) const { return *s == Char('\0'); } +}; + +template +struct at_eof : at_eof_base { + at_eof(const Char *start, std::size_t len) : end(start + len) {} + bool operator ()(const Char *s) const { return s == end; } + Char * end; +}; + +template +struct at_eof : at_eof_base { + at_eof(Iter, Iter end) : end(end) {} + bool operator ()(Iter i) const { return i == end; } + Iter end; +}; + +template +struct at_eof> : at_eof_base { + using ios_type = std::basic_istringstream; + at_eof(ios_type&) {} + bool operator ()(const ios_type &ss) const { return ss.eof(); } +}; + +template +at_eof(T &&...t) -> at_eof>...>; + +template +auto within_memory(const T &) { + return is_not(anything()); +} + +template +auto within_memory(const T &t) { + return in_interval(&*std::begin(t), &*std::end(t), interval::closed); +} + +template +auto within_memory(const T *t) { + return in_interval(t, t + bencode::detail::any_strlen(t), interval::closed); +} + +template +auto thrown_nested(Matcher &&matcher) { + auto what = exception_what(std::forward(matcher)); + std::ostringstream ss; + ss << "nested: " << type_name() << "(" << what.desc() << ")"; + + return basic_matcher([matcher = std::move(what)](const auto &actual) -> bool { + try { + actual.rethrow_nested(); + return false; + } catch(const Nested &e) { + return matcher(e); + } catch(...) { + return false; + } + }, ss.str()); +} + +template +auto decode_error(const std::string &what, std::size_t offset) { + std::string outer_what = what + ", at offset " + std::to_string(offset); + return thrown_raw( + all(exception_what(outer_what), thrown_nested(what)) + ); +} + +#endif diff --git a/test/test_decode.cpp b/test/test_decode.cpp deleted file mode 100644 index 4c37377..0000000 --- a/test/test_decode.cpp +++ /dev/null @@ -1,624 +0,0 @@ -#include -using namespace mettle; - -#include "bencode.hpp" - -template -struct char_type { - using type = typename T::value_type; -}; - -template -struct char_type { using type = std::remove_cv_t; }; -template -struct char_type> { using type = Char; }; - -template -inline constexpr bool supports_view = - !std::is_same_v::type, std::byte>; -template -inline constexpr bool supports_view> = false; - -template -T make_data(const char *data) { - using Char = typename char_type::type; - if constexpr(std::is_constructible_v) - return T((Char*)(data)); - else - return T((Char*)data, (Char*)(data + std::strlen(data))); -} - -struct at_eof_base : matcher_tag { - std::string desc() const { - return "at eof"; - } -}; - -template -struct at_eof; - -template -struct at_eof : at_eof_base { - at_eof(const Char *) {} - bool operator ()(const Char *s) const { return *s == Char('\0'); } -}; - -template -struct at_eof : at_eof_base { - at_eof(const Char *start, std::size_t len) : end(start + len) {} - bool operator ()(const Char *s) const { return s == end; } - Char * end; -}; - -template -struct at_eof : at_eof_base { - at_eof(Iter, Iter end) : end(end) {} - bool operator ()(Iter i) const { return i == end; } - Iter end; -}; - -template -struct at_eof> : at_eof_base { - using ios_type = std::basic_istringstream; - at_eof(ios_type&) {} - bool operator ()(const ios_type &ss) const { return ss.eof(); } -}; - -template -at_eof(T &&...t) -> at_eof>...>; - -template -auto within_memory(const T &) { - return is_not(anything()); -} - -template -auto within_memory(const T &t) { - return in_interval(&*std::begin(t), &*std::end(t), interval::closed); -} - -template -auto within_memory(const T *t) { - return in_interval(t, t + bencode::detail::any_strlen(t), interval::closed); -} - -template -auto thrown_nested(Matcher &&matcher) { - auto what = exception_what(std::forward(matcher)); - std::ostringstream ss; - ss << "nested: " << type_name() << "(" << what.desc() << ")"; - - return basic_matcher([matcher = std::move(what)](const auto &actual) -> bool { - try { - actual.rethrow_nested(); - return false; - } catch(const Nested &e) { - return matcher(e); - } catch(...) { - return false; - } - }, ss.str()); -} - -template -auto decode_error(const std::string &what, std::size_t offset) { - std::string outer_what = what + ", at offset " + std::to_string(offset); - return thrown_raw( - all(exception_what(outer_what), thrown_nested(what)) - ); -} - -template -auto mix_decoder(DecodeArgs &&decode_args, DoDecode &&do_decode) { - return [decode_args, do_decode](auto &&data) { - return std::apply( - do_decode, decode_args(std::forward(data)) - ); - }; -} - -template -auto decode_tests(Builder &_, Callable &&decode) { - using boost::get; - using std::get; - - using OutType = decltype(decode(std::declval())); - using Integer = typename OutType::integer; - using String = typename OutType::string; - using List = typename OutType::list; - using Dict = typename OutType::dict; - auto S = &make_data; - - _.test("integer", [decode]() { - auto pos = make_data("i42e"); - auto pos_value = decode(pos); - expect(get(pos_value), equal_to(42)); - - auto neg = make_data("i-42e"); - auto neg_value = decode(neg); - expect(get(neg_value), equal_to(-42)); - }); - - _.test("string", [decode, S]() { - auto data = make_data("4:spam"); - // Silence GCC < 10. - [[maybe_unused]] auto within_data_memory = within_memory(data); - - auto value = decode(data); - auto str = get(value); - expect(str, equal_to(S("spam"))); - if constexpr(bencode::detail::is_view) { - expect(&*str.begin(), within_data_memory); - expect(&*str.end(), within_data_memory); - } - }); - - _.test("list", [decode]() { - auto data = make_data("li42ee"); - auto value = decode(data); - auto list = get(value); - expect(get(list[0]), equal_to(42)); - }); - - _.test("dict", [decode, S]() { - auto data = make_data("d4:spami42ee"); - // Silence GCC < 10. - [[maybe_unused]] auto within_data_memory = within_memory(data); - - auto value = decode(data); - auto dict = get(value); - expect(get(dict[S("spam")]), equal_to(42)); - - auto str = dict.find(S("spam"))->first; - expect(str, equal_to(S("spam"))); - if constexpr(bencode::detail::is_view) { - expect(&*str.begin(), within_data_memory); - expect(&*str.end(), within_data_memory); - } - }); - - _.test("nested", [decode, S]() { - auto data = make_data( - "d" - "3:one" "i1e" - "5:three" "l" "d" "3:bar" "i0e" "3:foo" "i0e" "e" "e" - "3:two" "l" "i3e" "3:foo" "i4e" "e" - "e" - ); - auto value = decode(data); - auto dict = get(value); - expect(get(dict[S("one")]), equal_to(1)); - expect(get(get(dict[S("two")])[1]), equal_to(S("foo"))); - expect(get(get(get(dict[S("three")])[0])[S("foo")]), - equal_to(0)); - }); -} - -template -struct decode_suites { - decode_suites(DecodeArgs decode_args) - : decode_args(std::move(decode_args)) {} - DecodeArgs decode_args; - - template - void operator ()(Builder &_) const { - using InType = fixture_type_t; - using Char = typename char_type::type; - auto &decode_args = this->decode_args; - - subsuite<>(_, "decode", [decode_args](auto &_) { - decode_tests(_, mix_decoder(decode_args, - [](auto &&data, auto &&...rest) { - auto value = bencode::decode(data, rest...); - if constexpr(std::is_base_of_v) - expect(data, at_eof(data)); - return value; - })); - }); - - if constexpr(supports_view) { - subsuite<>(_, "decode_view", [decode_args](auto &_) { - decode_tests(_, mix_decoder(decode_args, - [](auto &&data, auto &&...rest) { - return bencode::decode_view(data, rest...); - })); - }); - } - - subsuite<>(_, "boost_decode", [decode_args](auto &_) { - decode_tests(_, mix_decoder(decode_args, - [](auto &&data, auto &&...rest) { - auto value = bencode::boost_decode(data, rest...); - if constexpr(std::is_base_of_v) - expect(data, at_eof(data)); - return value; - })); - }); - - if constexpr(supports_view) { - subsuite<>(_, "boost_decode_view", [decode_args](auto &_) { - decode_tests(_, mix_decoder(decode_args, - [](auto &&data, auto &&...rest) { - return bencode::boost_decode_view(data, rest...); - })); - }); - } - - subsuite< - bencode::data_for_char_t, bencode::boost_data_for_char_t - >(_, "basic_decode to", type_only, [decode_args](auto &_) { - using OutType = fixture_type_t; - decode_tests(_, mix_decoder(decode_args, - [](auto &&data, auto &&...rest) { - auto value = bencode::basic_decode(data, rest...); - if constexpr(std::is_base_of_v) - expect(data, at_eof(data)); - return value; - })); - }); - - if constexpr(supports_view) { - subsuite< - bencode::data_view_for_char_t, - bencode::boost_data_view_for_char_t - >(_, "basic_decode to", type_only, [decode_args](auto &_) { - using OutType = fixture_type_t; - decode_tests(_, mix_decoder(decode_args, - [](auto &&data, auto &&...rest) { - auto value = bencode::basic_decode(data, rest...); - if constexpr(std::is_base_of_v) - expect(data, at_eof(data)); - return value; - })); - }); - } - } -}; - -template -auto decode_some_tests(Builder &_, DecodeArgs &&decode_args, - DoDecode &&do_decode) { - using boost::get; - using std::get; - - _.test("successive objects", [decode_args, do_decode]() { - auto data = make_data("i42e4:goat"); - auto args = decode_args(data); - auto at_end = std::apply([](auto &&...t) { - return at_eof(std::forward(t)...); - }, args); - - auto first = std::apply(do_decode, args); - using OutType = decltype(first); - using Integer = typename OutType::integer; - using String = typename OutType::string; - auto S = &make_data; - - expect(get(first), equal_to(42)); - expect(std::get<0>(args), is_not(at_end)); - - auto second = std::apply(do_decode, args); - expect(get(second), equal_to(S("goat"))); - expect(std::get<0>(args), at_end); - }); -} - -template -struct decode_some_suites { - decode_some_suites(DecodeArgs decode_args) - : decode_args(std::move(decode_args)) {} - DecodeArgs decode_args; - - template - void operator ()(Builder &_) const { - using InType = fixture_type_t; - using Char = typename char_type::type; - auto &decode_args = this->decode_args; - - subsuite<>(_, "decode_some", [decode_args](auto &_) { - decode_tests( - _, mix_decoder(decode_args, [](auto &&data, auto &&...rest) { - auto at_end = at_eof(data, rest...); - auto value = bencode::decode_some(data, rest...); - expect(data, at_end); - return value; - }) - ); - decode_some_tests( - _, decode_args, [](auto &&data, auto &&...rest) { - return bencode::decode_some(data, rest...); - } - ); - }); - - if constexpr(supports_view) { - subsuite<>(_, "decode_view_some", [decode_args](auto &_) { - decode_tests( - _, mix_decoder(decode_args, [](auto &&data, auto &&...rest) { - auto at_end = at_eof(data, rest...); - auto value = bencode::decode_view_some(data, rest...); - expect(data, at_end); - return value; - }) - ); - decode_some_tests( - _, decode_args, [](auto &&data, auto &&...rest) { - return bencode::decode_view_some(data, rest...); - } - ); - }); - } - - subsuite<>(_, "boost_decode_some", [decode_args](auto &_) { - decode_tests( - _, mix_decoder(decode_args, [](auto &&data, auto &&...rest) { - auto at_end = at_eof(data, rest...); - auto value = bencode::boost_decode_some(data, rest...); - expect(data, at_end); - return value; - }) - ); - decode_some_tests( - _, decode_args, [](auto &&data, auto &&...rest) { - return bencode::boost_decode_some(data, rest...); - } - ); - }); - - if constexpr(supports_view) { - subsuite<>(_, "boost_decode_view_some", [decode_args](auto &_) { - decode_tests( - _, mix_decoder(decode_args, [](auto &&data, auto &&...rest) { - auto at_end = at_eof(data, rest...); - auto value = bencode::boost_decode_view_some(data, rest...); - expect(data, at_end); - return value; - }) - ); - decode_some_tests( - _, decode_args, [](auto &&data, auto &&...rest) { - return bencode::boost_decode_view_some(data, rest...); - } - ); - }); - } - - subsuite< - bencode::data_for_char_t, bencode::boost_data_for_char_t - >(_, "basic_decode_some to", type_only, [decode_args](auto &_) { - using OutType = fixture_type_t; - decode_tests( - _, mix_decoder(decode_args, [](auto &&data, auto &&...rest) { - auto at_end = at_eof(data, rest...); - auto value = bencode::basic_decode_some(data, rest...); - expect(data, at_end); - return value; - }) - ); - decode_some_tests( - _, decode_args, [](auto &&data, auto &&...rest) { - return bencode::basic_decode_some(data, rest...); - } - ); - }); - - if constexpr(supports_view) { - subsuite< - bencode::data_view_for_char_t, - bencode::boost_data_view_for_char_t - >(_, "basic_decode_some to", type_only, [decode_args](auto &_) { - using OutType = fixture_type_t; - decode_tests( - _, mix_decoder(decode_args, [](auto &&data, auto &&...rest) { - auto at_end = at_eof(data, rest...); - auto value = bencode::basic_decode_some(data, rest...); - expect(data, at_end); - return value; - }) - ); - decode_some_tests( - _, decode_args, [](auto &&data, auto &&...rest) { - return bencode::basic_decode_some(data, rest...); - } - ); - }); - } - } -}; - -suite<> test_decode("test decoder", [](auto &_) { - using std::istringstream; -#if __cpp_char8_t - using u8istringstream = std::basic_istringstream; -#endif - - auto decode_args = [](auto &&data) { - return std::tuple(data); - }; - auto decode_iter_args = [](auto &&data) { - return std::make_tuple(data.begin(), data.end()); - }; - auto decode_ptr_len_args = [](auto &&data) { - return std::tuple( - data, bencode::detail::any_strlen(data) - ); - }; - - subsuite< - const char *, std::string, std::vector, istringstream, -#if __cpp_char8_t - const char8_t *, std::u8string, std::vector, u8istringstream, -#endif - const std::byte *, std::vector - >(_, "decode", type_only, decode_suites(decode_args)); - - subsuite< - std::string, std::vector, -#if __cpp_char8_t - std::u8string, std::vector, -#endif - std::vector - >(_, "decode iterator pair", type_only, decode_suites(decode_iter_args)); - - subsuite< - const char *, -#if __cpp_char8_t - const char8_t *, -#endif - const std::byte * - >(_, "decode pointer/length", type_only, decode_suites(decode_ptr_len_args)); - - subsuite< - const char *, istringstream, -#if __cpp_char8_t - const char8_t *, u8istringstream, -#endif - const std::byte * - >(_, "decode_some", type_only, decode_some_suites(decode_args)); - - subsuite< - std::string, std::vector, -#if __cpp_char8_t - std::u8string, std::vector, -#endif - std::vector - >(_, "decode_some iterator pair", type_only, - decode_some_suites([](auto &&data) { - return std::make_tuple(data.begin(), data.end()); - }) - ); - - subsuite< - const char *, -#if __cpp_char8_t - const char8_t *, -#endif - const std::byte * - >(_, "decode_some pointer/length", type_only, - decode_some_suites(decode_ptr_len_args)); - - subsuite<>(_, "decoding integers", [](auto &_) { - using udata = bencode::basic_data< - std::variant, unsigned long long, std::string, std::vector, - bencode::map_proxy - >; - - _.test("max value", []() { - auto value = bencode::decode("i9223372036854775807e"); - expect(std::get(value), - equal_to(9223372036854775807LL)); - }); - - _.test("overflow", []() { - expect([]() { bencode::decode("i9223372036854775808e"); }, - decode_error("integer overflow", 20)); - expect([]() { bencode::decode("i9323372036854775807e"); }, - decode_error("integer overflow", 20)); - expect([]() { bencode::decode("i92233720368547758070e"); }, - decode_error("integer overflow", 20)); - }); - - _.test("min value", []() { - auto value = bencode::decode("i-9223372036854775808e"); - expect(std::get(value), - equal_to(-9223372036854775807LL - 1)); - }); - - _.test("underflow", []() { - expect([]() { bencode::decode("i-9223372036854775809e"); }, - decode_error("integer underflow", 21)); - expect([]() { bencode::decode("i-9323372036854775808e"); }, - decode_error("integer underflow", 21)); - expect([]() { bencode::decode("i-92233720368547758080e"); }, - decode_error("integer underflow", 21)); - }); - - _.test("max value (unsigned)", []() { - auto value = bencode::basic_decode("i18446744073709551615e"); - expect(std::get(value), - equal_to(18446744073709551615ULL)); - }); - - _.test("overflow (unsigned)", []() { - expect([]() { - bencode::basic_decode("i18446744073709551616e"); - }, decode_error("integer overflow", 21)); - expect([]() { - bencode::basic_decode("i19446744073709551615e"); - }, decode_error("integer overflow", 21)); - expect([]() { - bencode::basic_decode("i184467440737095516150e"); - }, decode_error("integer overflow", 21)); - }); - - _.test("negative value (unsigned)", []() { - expect( - []() { bencode::basic_decode("i-42e"); }, - decode_error("expected unsigned integer", 1) - ); - }); - }); - - subsuite<>(_, "error handling", [](auto &_) { - _.test("unexpected type token", []() { - expect([]() { bencode::decode("x"); }, - decode_error("unexpected type token", 0)); - }); - - _.test("unexpected end of input", []() { - auto eos = [](std::size_t offset) { - return decode_error( - "unexpected end of input", offset - ); - }; - - expect([]() { bencode::decode(""); }, eos(0)); - expect([]() { bencode::decode("i123"); }, eos(4)); - expect([]() { bencode::decode("3"); }, eos(1)); - expect([]() { bencode::decode("3:as"); }, eos(4)); - expect([]() { bencode::decode("l"); }, eos(1)); - expect([]() { bencode::decode("li1e"); }, eos(4)); - expect([]() { bencode::decode("d"); }, eos(1)); - expect([]() { bencode::decode("d1:a"); }, eos(4)); - expect([]() { bencode::decode("d1:ai1e"); }, eos(7)); - }); - - _.test("extraneous character", []() { - expect([]() { bencode::decode("i123ei"); }, - decode_error("extraneous character", 5)); - }); - - _.test("expected 'e' token", []() { - expect([]() { bencode::decode("i123i"); }, - decode_error("expected 'e' token", 4)); - }); - - _.test("unexpected 'e' token", []() { - expect([]() { bencode::decode("e"); }, - decode_error("unexpected 'e' token", 0)); - }); - - _.test("expected ':' token", []() { - expect([]() { bencode::decode("1abc"); }, - decode_error("expected ':' token", 1)); - }); - - _.test("expected string start token", []() { - expect( - []() { bencode::decode("di123ee"); }, - decode_error( - "expected string start token for dict key", 1 - ) - ); - }); - - _.test("duplicated key", []() { - expect([]() { bencode::decode("d3:fooi1e3:fooi1ee"); }, - decode_error( - "duplicated key in dict: \"foo\"", 17 - )); - }); - }); - -});