boost/libs/json/test/test.hpp
2021-10-05 21:37:46 +02:00

1113 lines
21 KiB
C++

//
// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/json
//
#ifndef BOOST_JSON_TEST_HPP
#define BOOST_JSON_TEST_HPP
#include <boost/json/basic_parser.hpp>
#include <boost/json/value.hpp>
#include <boost/json/serializer.hpp>
#include <boost/json/storage_ptr.hpp>
#include <boost/json/detail/format.hpp>
#include <cstddef>
#include <iterator>
#include <memory>
#include <type_traits>
#include "test_suite.hpp"
BOOST_JSON_NS_BEGIN
//----------------------------------------------------------
struct test_failure : std::exception
{
virtual
char const*
what() const noexcept override
{
return "test failure";
}
};
struct fail_resource
: memory_resource
{
std::size_t fail_max = 0;
std::size_t fail = 0;
std::size_t nalloc = 0;
std::size_t bytes = 0;
~fail_resource()
{
BOOST_TEST(nalloc == 0);
}
void*
do_allocate(
std::size_t n,
std::size_t) override
{
if(++fail == fail_max)
{
++fail_max;
fail = 0;
throw test_failure{};
}
auto p = ::operator new(n);
++nalloc;
bytes += n;
return p;
}
void
do_deallocate(
void* p,
std::size_t n,
std::size_t) noexcept override
{
if(BOOST_TEST(nalloc > 0))
--nalloc;
bytes -= n;
::operator delete(p);
}
bool
do_is_equal(
memory_resource const& mr) const noexcept override
{
return this == &mr;
}
};
template<class F>
void
fail_loop(F&& f)
{
fail_resource ss;
ss.fail_max = 1;
while(ss.fail < 200)
{
try
{
f(&ss);
}
catch(test_failure const&)
{
continue;
}
break;
}
BOOST_TEST(ss.fail < 200);
}
//----------------------------------------------------------
struct unique_resource
: memory_resource
{
unique_resource() = default;
void*
do_allocate(
std::size_t n,
std::size_t) override
{
return ::operator new(n);
}
void
do_deallocate(
void* p,
std::size_t,
std::size_t) noexcept override
{
return ::operator delete(p);
}
bool
do_is_equal(
memory_resource const& mr) const noexcept override
{
return this == &mr;
}
};
//----------------------------------------------------------
// The null parser discards all the data
class null_parser
{
struct handler
{
constexpr static std::size_t max_object_size = std::size_t(-1);
constexpr static std::size_t max_array_size = std::size_t(-1);
constexpr static std::size_t max_key_size = std::size_t(-1);
constexpr static std::size_t max_string_size = std::size_t(-1);
bool on_document_begin( error_code& ) { return true; }
bool on_document_end( error_code& ) { return true; }
bool on_object_begin( error_code& ) { return true; }
bool on_object_end( std::size_t, error_code& ) { return true; }
bool on_array_begin( error_code& ) { return true; }
bool on_array_end( std::size_t, error_code& ) { return true; }
bool on_key_part( string_view, std::size_t, error_code& ) { return true; }
bool on_key( string_view, std::size_t, error_code& ) { return true; }
bool on_string_part( string_view, std::size_t, error_code& ) { return true; }
bool on_string( string_view, std::size_t, error_code& ) { return true; }
bool on_number_part( string_view, error_code&) { return true; }
bool on_int64( std::int64_t, string_view, error_code& ) { return true; }
bool on_uint64( std::uint64_t, string_view, error_code& ) { return true; }
bool on_double( double, string_view, error_code& ) { return true; }
bool on_bool( bool, error_code& ) { return true; }
bool on_null( error_code& ) { return true; }
bool on_comment_part( string_view, error_code& ) { return true; }
bool on_comment( string_view, error_code& ) { return true; }
};
basic_parser<handler> p_;
public:
null_parser()
: p_(parse_options())
{
}
explicit
null_parser(parse_options po)
: p_(po)
{
}
void
reset()
{
p_.reset();
}
std::size_t
write(
char const* data,
std::size_t size,
error_code& ec)
{
auto const n = p_.write_some(
false, data, size, ec);
if(! ec && n < size)
ec = error::extra_data;
return n;
}
};
//----------------------------------------------------------
class fail_parser
{
struct handler
{
constexpr static std::size_t max_object_size = std::size_t(-1);
constexpr static std::size_t max_array_size = std::size_t(-1);
constexpr static std::size_t max_key_size = std::size_t(-1);
constexpr static std::size_t max_string_size = std::size_t(-1);
std::size_t n;
handler()
: n(std::size_t(-1))
{
}
bool
maybe_fail(error_code& ec)
{
if(n && --n > 0)
return true;
ec = error::test_failure;
return false;
}
bool
on_document_begin(
error_code& ec)
{
return maybe_fail(ec);
}
bool
on_document_end(
error_code& ec)
{
return maybe_fail(ec);
}
bool
on_object_begin(
error_code& ec)
{
return maybe_fail(ec);
}
bool
on_object_end(
std::size_t,
error_code& ec)
{
return maybe_fail(ec);
}
bool
on_array_begin(
error_code& ec)
{
return maybe_fail(ec);
}
bool
on_array_end(
std::size_t,
error_code& ec)
{
return maybe_fail(ec);
}
bool
on_key_part(
string_view,
std::size_t,
error_code& ec)
{
return maybe_fail(ec);
}
bool
on_key(
string_view,
std::size_t,
error_code& ec)
{
return maybe_fail(ec);
}
bool
on_string_part(
string_view,
std::size_t,
error_code& ec)
{
return maybe_fail(ec);
}
bool
on_string(
string_view,
std::size_t,
error_code& ec)
{
return maybe_fail(ec);
}
bool
on_number_part(
string_view,
error_code& ec)
{
return maybe_fail(ec);
}
bool
on_int64(
int64_t,
string_view,
error_code& ec)
{
return maybe_fail(ec);
}
bool
on_uint64(
uint64_t,
string_view,
error_code& ec)
{
return maybe_fail(ec);
}
bool
on_double(
double,
string_view,
error_code& ec)
{
return maybe_fail(ec);
}
bool
on_bool(
bool,
error_code& ec)
{
return maybe_fail(ec);
}
bool
on_null(error_code& ec)
{
return maybe_fail(ec);
}
bool
on_comment_part(
string_view,
error_code& ec)
{
return maybe_fail(ec);
}
bool
on_comment(
string_view,
error_code& ec)
{
return maybe_fail(ec);
}
};
basic_parser<handler> p_;
public:
fail_parser()
: p_(parse_options())
{
}
explicit
fail_parser(
std::size_t n,
parse_options po = parse_options())
: p_(po)
{
p_.handler().n = n;
}
explicit
fail_parser(parse_options po)
: p_(po)
{
}
void
reset()
{
p_.reset();
}
bool
done() const noexcept
{
return p_.done();
}
std::size_t
write_some(
bool more,
char const* data,
std::size_t size,
error_code& ec)
{
return p_.write_some(
more, data, size, ec);
}
std::size_t
write(
bool more,
char const* data,
std::size_t size,
error_code& ec)
{
auto const n = p_.write_some(
more, data, size, ec);
if(! ec && n < size)
ec = error::extra_data;
return n;
}
};
//----------------------------------------------------------
struct test_exception
: std::exception
{
char const*
what() const noexcept
{
return "test exception";
}
};
// Exercises every exception path
class throw_parser
{
struct handler
{
constexpr static std::size_t max_object_size = std::size_t(-1);
constexpr static std::size_t max_array_size = std::size_t(-1);
constexpr static std::size_t max_key_size = std::size_t(-1);
constexpr static std::size_t max_string_size = std::size_t(-1);
std::size_t n;
handler()
: n(std::size_t(-1))
{
}
bool
maybe_throw()
{
if(n && --n > 0)
return true;
throw test_exception{};
}
bool
on_document_begin(
error_code&)
{
return maybe_throw();
}
bool
on_document_end(
error_code&)
{
return maybe_throw();
}
bool
on_object_begin(
error_code&)
{
return maybe_throw();
}
bool
on_object_end(
std::size_t,
error_code&)
{
return maybe_throw();
}
bool
on_array_begin(
error_code&)
{
return maybe_throw();
}
bool
on_array_end(
std::size_t,
error_code&)
{
return maybe_throw();
}
bool
on_key_part(
string_view,
std::size_t,
error_code&)
{
return maybe_throw();
}
bool
on_key(
string_view,
std::size_t,
error_code&)
{
return maybe_throw();
}
bool
on_string_part(
string_view,
std::size_t,
error_code&)
{
return maybe_throw();
}
bool
on_string(
string_view,
std::size_t,
error_code&)
{
return maybe_throw();
}
bool
on_number_part(
string_view,
error_code&)
{
return maybe_throw();
}
bool
on_int64(
int64_t,
string_view,
error_code&)
{
return maybe_throw();
}
bool
on_uint64(
uint64_t,
string_view,
error_code&)
{
return maybe_throw();
}
bool
on_double(
double,
string_view,
error_code&)
{
return maybe_throw();
}
bool
on_bool(
bool,
error_code&)
{
return maybe_throw();
}
bool
on_null(error_code&)
{
return maybe_throw();
}
bool
on_comment_part(
string_view,
error_code&)
{
return maybe_throw();
}
bool
on_comment(
string_view,
error_code&)
{
return maybe_throw();
}
};
basic_parser<handler> p_;
public:
throw_parser()
: p_(parse_options())
{
}
explicit
throw_parser(
std::size_t n,
parse_options po = parse_options())
: p_(po)
{
p_.handler().n = n;
}
explicit
throw_parser(parse_options po)
: p_(po)
{
}
void
reset() noexcept
{
p_.reset();
}
std::size_t
write(
bool more,
char const* data,
std::size_t size,
error_code& ec)
{
auto const n = p_.write_some(
more, data, size, ec);
if(! ec && n < size)
ec = error::extra_data;
return n;
}
};
//----------------------------------------------------------
// wrap an iterator to make an input iterator
template<class FwdIt>
class input_iterator
{
FwdIt it_;
public:
using value_type = typename std::iterator_traits<FwdIt>::value_type;
using pointer = typename std::iterator_traits<FwdIt>::pointer;
using reference = typename std::iterator_traits<FwdIt>::reference;
using difference_type = typename std::iterator_traits<FwdIt>::difference_type;
using iterator_category = std::input_iterator_tag;
input_iterator() = default;
input_iterator(input_iterator const&) = default;
input_iterator& operator=(
input_iterator const&) = default;
input_iterator(FwdIt it)
: it_(it)
{
}
input_iterator&
operator++() noexcept
{
++it_;
return *this;
}
input_iterator
operator++(int) noexcept
{
auto tmp = *this;
++*this;
return tmp;
}
pointer
operator->() const noexcept
{
return it_.operator->();
}
reference
operator*() const noexcept
{
return *it_;
}
bool
operator==(input_iterator other) const noexcept
{
return it_ == other.it_;
}
bool
operator!=(input_iterator other) const noexcept
{
return it_ != other.it_;
}
};
template<class FwdIt>
input_iterator<FwdIt>
make_input_iterator(FwdIt it)
{
return input_iterator<FwdIt>(it);
}
//----------------------------------------------------------
inline
bool
equal_storage(
value const& v,
storage_ptr const& sp);
inline
bool
equal_storage(
object const& o,
storage_ptr const& sp)
{
if(*o.storage() != *sp)
return false;
for(auto const& e : o)
if(! equal_storage(e.value(), sp))
return false;
return true;
}
inline
bool
equal_storage(
array const& a,
storage_ptr const& sp)
{
if(*a.storage() != *sp)
return false;
for(auto const& v : a)
if(! equal_storage(v, sp))
return false;
return true;
}
bool
equal_storage(
value const& v,
storage_ptr const& sp)
{
switch(v.kind())
{
case json::kind::object:
if(*v.as_object().storage() != *sp)
return false;
return equal_storage(v.as_object(), sp);
case json::kind::array:
if(*v.as_array().storage() != *sp)
return false;
return equal_storage(v.as_array(), sp);
case json::kind::string:
return *v.as_string().storage() == *sp;
case json::kind::int64:
case json::kind::uint64:
case json::kind::double_:
case json::kind::bool_:
case json::kind::null:
break;
}
return *v.storage() == *sp;
}
inline
void
check_storage(
object const& o,
storage_ptr const& sp)
{
BOOST_TEST(equal_storage(o, sp));
}
inline
void
check_storage(
array const& a,
storage_ptr const& sp)
{
BOOST_TEST(equal_storage(a, sp));
}
inline
void
check_storage(
value const& v,
storage_ptr const& sp)
{
BOOST_TEST(equal_storage(v, sp));
}
//----------------------------------------------------------
namespace detail {
inline
void
to_string_test(
string& dest,
json::value const& jv)
{
switch(jv.kind())
{
case kind::object:
{
dest.push_back('{');
auto const& obj(
jv.get_object());
auto it = obj.begin();
if(it != obj.end())
{
goto obj_first;
while(it != obj.end())
{
dest.push_back(',');
obj_first:
dest.push_back('\"');
dest.append(it->key());
dest.push_back('\"');
dest.push_back(':');
to_string_test(
dest, it->value());
++it;
}
}
dest.push_back('}');
break;
}
case kind::array:
{
dest.push_back('[');
auto const& arr(
jv.get_array());
auto it = arr.begin();
if(it != arr.end())
{
goto arr_first;
while(it != arr.end())
{
dest.push_back(',');
arr_first:
to_string_test(
dest, *it);
++it;
}
}
dest.push_back(']');
break;
}
case kind::string:
#if 1
// safe, but doesn't handle escsapes
dest.push_back('\"');
dest.append(jv.get_string());
dest.push_back('\"');
#else
dest.append(serialize(jv));
#endif
break;
case kind::int64:
{
char buf[detail::max_number_chars];
auto const n =
detail::format_int64(
buf, jv.as_int64());
dest.append(string_view(buf).substr(0, n));
break;
}
case kind::uint64:
{
char buf[detail::max_number_chars];
auto const n =
detail::format_uint64(
buf, jv.as_uint64());
dest.append(string_view(buf).substr(0, n));
break;
}
case kind::double_:
{
char buf[detail::max_number_chars];
auto const n =
detail::format_double(
buf, jv.as_double());
dest.append(string_view(buf).substr(0, n));
break;
}
case kind::bool_:
if(jv.as_bool())
dest.append("true");
else
dest.append("false");
break;
case kind::null:
dest.append("null");
break;
default:
// should never get here
dest.append("?");
break;
}
}
} // detail
inline
string
to_string_test(
json::value const& jv)
{
string s;
s.reserve(1024);
detail::to_string_test(s, jv);
return s;
}
//----------------------------------------------------------
inline
bool
equal(
value const& lhs,
value const& rhs)
{
if(lhs.kind() != rhs.kind())
return false;
switch(lhs.kind())
{
case kind::object:
{
auto const& obj1 =
lhs.get_object();
auto const& obj2 =
rhs.get_object();
auto n = obj1.size();
if(obj2.size() != n)
return false;
auto it1 = obj1.begin();
auto it2 = obj2.begin();
while(n--)
{
if( it1->key() !=
it2->key())
return false;
if(! equal(
it1->value(),
it2->value()))
return false;
++it1;
++it2;
}
return true;
}
case kind::array:
{
auto const& arr1 =
lhs.get_array();
auto const& arr2 =
rhs.get_array();
auto n = arr1.size();
if(arr2.size() != n)
return false;
auto it1 = arr1.begin();
auto it2 = arr2.begin();
while(n--)
if(! equal(*it1++, *it2++))
return false;
return true;
}
case kind::string:
return
lhs.get_string() ==
rhs.get_string();
case kind::double_:
return
lhs.get_double() ==
rhs.get_double();
case kind::int64:
return
lhs.get_int64() ==
rhs.get_int64();
case kind::uint64:
return
lhs.get_uint64() ==
rhs.get_uint64();
case kind::bool_:
return
lhs.get_bool() ==
rhs.get_bool();
case kind::null:
return true;
}
return false;
}
//----------------------------------------------------------
namespace detail {
inline
void
check_array_impl(int, value const&)
{
}
template<class Arg>
void
check_array_impl(
int i, value const& jv,
Arg const& arg)
{
BOOST_TEST(equal(jv.at(i), arg));
}
template<
class Arg0,
class Arg1,
class... Argn>
void
check_array_impl(
int i, value const& jv,
Arg0 const& arg0,
Arg1 const& arg1,
Argn const&... argn)
{
BOOST_TEST(equal(jv.at(i), arg0));
BOOST_TEST(equal(jv.at(i + 1), arg1));
check_array_impl(i + 2, jv, argn...);
}
} // detail
template<class... Argn>
static
void
check_array(
value const& jv,
Argn const&... argn)
{
if(! BOOST_TEST(jv.is_array()))
return;
if(! BOOST_TEST(sizeof...(argn) ==
jv.get_array().size()))
return;
detail::check_array_impl(0, jv, argn...);
}
//----------------------------------------------------------
BOOST_JSON_NS_END
#endif