Skip to content

Commit

Permalink
Split up decoding tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jimporter committed Jul 23, 2024
1 parent f3ce4b3 commit 60ba84e
Show file tree
Hide file tree
Showing 7 changed files with 664 additions and 624 deletions.
139 changes: 139 additions & 0 deletions test/decode/decode.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#ifndef INC_BENCODE_TEST_DECODE_DECODE_HPP
#define INC_BENCODE_TEST_DECODE_DECODE_HPP

#include <sstream>
#include <tuple>
#include <utility>

#include "bencode.hpp"

using std::istringstream;
using bistringstream = std::basic_istringstream<std::byte>;
#if __cpp_char8_t
using u8istringstream = std::basic_istringstream<char8_t>;
#endif

template<typename T>
struct char_type {
using type = typename T::value_type;
};

template<typename Char>
struct char_type<Char *> { using type = std::remove_cv_t<Char>; };
template<typename Char>
struct char_type<std::basic_istringstream<Char>> { using type = Char; };

template<typename T>
inline constexpr bool supports_view =
!std::is_same_v<typename char_type<T>::type, std::byte>;
template<typename Char>
inline constexpr bool supports_view<std::basic_istringstream<Char>> = false;

template<typename T>
T make_data(const char *data) {
using Char = typename char_type<T>::type;
if constexpr(std::is_constructible_v<T, const Char *>)
return T((Char*)(data));
else
return T((Char*)data, (Char*)(data + std::strlen(data)));
}

template<typename DecodeArgs, typename DoDecode>
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<decltype(data)>(data))
);
};
}

auto decode_args = [](auto &&data) {
return std::tuple<decltype(data)&>(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<decltype(data)&, std::size_t>(
data, bencode::detail::any_strlen(data)
);
};

template<typename InType, typename Builder, typename Callable>
auto decode_tests(Builder &_, Callable &&decode) {
using boost::get;
using std::get;

using OutType = decltype(decode(std::declval<InType>()));
using Integer = typename OutType::integer;
using String = typename OutType::string;
using List = typename OutType::list;
using Dict = typename OutType::dict;
auto S = &make_data<String>;

_.test("integer", [decode]() {
auto pos = make_data<InType>("i42e");
auto pos_value = decode(pos);
expect(get<Integer>(pos_value), equal_to(42));

auto neg = make_data<InType>("i-42e");
auto neg_value = decode(neg);
expect(get<Integer>(neg_value), equal_to(-42));
});

_.test("string", [decode, S]() {
auto data = make_data<InType>("4:spam");
// Silence GCC < 10.
[[maybe_unused]] auto within_data_memory = within_memory(data);

auto value = decode(data);
auto str = get<String>(value);
expect(str, equal_to(S("spam")));
if constexpr(bencode::detail::is_view_v<String>) {
expect(&*str.begin(), within_data_memory);
expect(&*str.end(), within_data_memory);
}
});

_.test("list", [decode]() {
auto data = make_data<InType>("li42ee");
auto value = decode(data);
auto list = get<List>(value);
expect(get<Integer>(list[0]), equal_to(42));
});

_.test("dict", [decode, S]() {
auto data = make_data<InType>("d4:spami42ee");
// Silence GCC < 10.
[[maybe_unused]] auto within_data_memory = within_memory(data);

auto value = decode(data);
auto dict = get<Dict>(value);
expect(get<Integer>(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_v<String>) {
expect(&*str.begin(), within_data_memory);
expect(&*str.end(), within_data_memory);
}
});

_.test("nested", [decode, S]() {
auto data = make_data<InType>(
"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<Dict>(value);
expect(get<Integer>(dict[S("one")]), equal_to(1));
expect(get<String>(get<List>(dict[S("two")])[1]), equal_to(S("foo")));
expect(get<Integer>(get<Dict>(get<List>(dict[S("three")])[0])[S("foo")]),
equal_to(0));
});
}

#endif
114 changes: 114 additions & 0 deletions test/decode/test_decode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#include <mettle.hpp>
using namespace mettle;

#include "bencode.hpp"

#include "../matchers.hpp"
#include "decode.hpp"

template<typename DecodeArgs>
struct decode_suites {
decode_suites(DecodeArgs decode_args)
: decode_args(std::move(decode_args)) {}
DecodeArgs decode_args;

template<typename Builder>
void operator ()(Builder &_) const {
using InType = fixture_type_t<decltype(_)>;
using Char = typename char_type<InType>::type;
auto &decode_args = this->decode_args;

subsuite<>(_, "decode", [decode_args](auto &_) {
decode_tests<InType>(_, mix_decoder(decode_args,
[](auto &&data, auto &&...rest) {
auto value = bencode::decode(data, rest...);
if constexpr(std::is_base_of_v<std::ios_base, InType>)
expect(data, at_eof(data));
return value;
}));
});

if constexpr(supports_view<InType>) {
subsuite<>(_, "decode_view", [decode_args](auto &_) {
decode_tests<InType>(_, mix_decoder(decode_args,
[](auto &&data, auto &&...rest) {
return bencode::decode_view(data, rest...);
}));
});
}

subsuite<>(_, "boost_decode", [decode_args](auto &_) {
decode_tests<InType>(_, mix_decoder(decode_args,
[](auto &&data, auto &&...rest) {
auto value = bencode::boost_decode(data, rest...);
if constexpr(std::is_base_of_v<std::ios_base, InType>)
expect(data, at_eof(data));
return value;
}));
});

if constexpr(supports_view<InType>) {
subsuite<>(_, "boost_decode_view", [decode_args](auto &_) {
decode_tests<InType>(_, mix_decoder(decode_args,
[](auto &&data, auto &&...rest) {
return bencode::boost_decode_view(data, rest...);
}));
});
}

subsuite<
bencode::data_for_char_t<Char>, bencode::boost_data_for_char_t<Char>
>(_, "basic_decode to", type_only, [decode_args](auto &_) {
using OutType = fixture_type_t<decltype(_)>;
decode_tests<InType>(_, mix_decoder(decode_args,
[](auto &&data, auto &&...rest) {
auto value = bencode::basic_decode<OutType>(data, rest...);
if constexpr(std::is_base_of_v<std::ios_base, InType>)
expect(data, at_eof(data));
return value;
}));
});

if constexpr(supports_view<InType>) {
subsuite<
bencode::data_view_for_char_t<Char>,
bencode::boost_data_view_for_char_t<Char>
>(_, "basic_decode to", type_only, [decode_args](auto &_) {
using OutType = fixture_type_t<decltype(_)>;
decode_tests<InType>(_, mix_decoder(decode_args,
[](auto &&data, auto &&...rest) {
auto value = bencode::basic_decode<OutType>(data, rest...);
if constexpr(std::is_base_of_v<std::ios_base, InType>)
expect(data, at_eof(data));
return value;
}));
});
}
}
};

suite<> test_decode("decode", [](auto &_) {
subsuite<
const char *, std::string, std::vector<char>, istringstream,
#if __cpp_char8_t
const char8_t *, std::u8string, std::vector<char8_t>, u8istringstream,
#endif
const std::byte *, std::vector<std::byte>, bistringstream
>(_, "decode", type_only, decode_suites(decode_args));

subsuite<
std::string, std::vector<char>,
#if __cpp_char8_t
std::u8string, std::vector<char8_t>,
#endif
std::vector<std::byte>
>(_, "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));
});
67 changes: 67 additions & 0 deletions test/decode/test_decode_errors.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#include <mettle.hpp>
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<bencode::syntax_error>("unexpected type token", 0));
});

_.test("unexpected end of input", []() {
auto eos = [](std::size_t offset) {
return decode_error<bencode::end_of_input_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<bencode::syntax_error>("extraneous character", 5));
});

_.test("expected 'e' token", []() {
expect([]() { bencode::decode("i123i"); },
decode_error<bencode::syntax_error>("expected 'e' token", 4));
});

_.test("unexpected 'e' token", []() {
expect([]() { bencode::decode("e"); },
decode_error<bencode::syntax_error>("unexpected 'e' token", 0));
});

_.test("expected ':' token", []() {
expect([]() { bencode::decode("1abc"); },
decode_error<bencode::syntax_error>("expected ':' token", 1));
});

_.test("expected string start token", []() {
expect(
[]() { bencode::decode("di123ee"); },
decode_error<bencode::syntax_error>(
"expected string start token for dict key", 1
)
);
});

_.test("duplicated key", []() {
expect([]() { bencode::decode("d3:fooi1e3:fooi1ee"); },
decode_error<bencode::syntax_error>(
"duplicated key in dict: \"foo\"", 17
));
});
});
68 changes: 68 additions & 0 deletions test/decode/test_decode_integers.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include <mettle.hpp>
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<bencode::integer>(value),
equal_to(9223372036854775807LL));
});

_.test("overflow", []() {
expect([]() { bencode::decode("i9223372036854775808e"); },
decode_error<std::overflow_error>("integer overflow", 20));
expect([]() { bencode::decode("i9323372036854775807e"); },
decode_error<std::overflow_error>("integer overflow", 20));
expect([]() { bencode::decode("i92233720368547758070e"); },
decode_error<std::overflow_error>("integer overflow", 20));
});

_.test("min value", []() {
auto value = bencode::decode("i-9223372036854775808e");
expect(std::get<bencode::integer>(value),
equal_to(-9223372036854775807LL - 1));
});

_.test("underflow", []() {
expect([]() { bencode::decode("i-9223372036854775809e"); },
decode_error<std::underflow_error>("integer underflow", 21));
expect([]() { bencode::decode("i-9323372036854775808e"); },
decode_error<std::underflow_error>("integer underflow", 21));
expect([]() { bencode::decode("i-92233720368547758080e"); },
decode_error<std::underflow_error>("integer underflow", 21));
});

_.test("max value (unsigned)", []() {
auto value = bencode::basic_decode<udata>("i18446744073709551615e");
expect(std::get<udata::integer>(value),
equal_to(18446744073709551615ULL));
});

_.test("overflow (unsigned)", []() {
expect([]() {
bencode::basic_decode<udata>("i18446744073709551616e");
}, decode_error<std::overflow_error>("integer overflow", 21));
expect([]() {
bencode::basic_decode<udata>("i19446744073709551615e");
}, decode_error<std::overflow_error>("integer overflow", 21));
expect([]() {
bencode::basic_decode<udata>("i184467440737095516150e");
}, decode_error<std::overflow_error>("integer overflow", 21));
});

_.test("negative value (unsigned)", []() {
expect(
[]() { bencode::basic_decode<udata>("i-42e"); },
decode_error<std::underflow_error>("expected unsigned integer", 1)
);
});
});
Loading

0 comments on commit 60ba84e

Please sign in to comment.