dxx-rebirth/common/unittest/valptridx-range.cpp

313 lines
14 KiB
C++
Raw Normal View History

#include "dxxsconf.h"
#include "compiler-poison.h"
/* If DXX_HAVE_POISON_UNDEFINED is not zero,
* array_managed_type::array_managed_type() is declared and must be
* defined out of line. These tests do not need to run any code at
* array construction time, so force DXX_HAVE_POISON_UNDEFINED to be zero.
*/
#undef DXX_HAVE_POISON_UNDEFINED
#define DXX_HAVE_POISON_UNDEFINED 0
#include "valptridx.h"
#include "valptridx.tcc"
#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE Rebirth valptridx
#include <boost/test/unit_test.hpp>
/* Convenience macro to prevent the compiler optimizer from performing
* constant propagation of the input. This ensures that build-time
* tests are suppressed, allowing the runtime test to be emitted and
* executed.
*/
#define OPTIMIZER_HIDE_VARIABLE(V) asm("" : "=rm" (V) : "0" (V) : "memory")
/* Convenience function to pass an anonymous value (such as an integer
* constant) through OPTIMIZER_HIDE_VARIABLE, then return it. This is
* needed for contexts where a bare asm() statement would not be legal.
*
* It also has the useful secondary effect of casting the input to a
* type, if the caller explicitly sets the typename T.
*/
template <typename T>
static inline T optimizer_hidden_variable(T v)
{
OPTIMIZER_HIDE_VARIABLE(v);
return v;
}
/* Convenience macro to ignore the result of an expression in a way that
* __attribute__((warn_unused_result)) does not warn that the result was
* ignored.
*/
#define DXX_TEST_VALPTRIDX_IGNORE_RETURN(EXPR) ({ auto &&r = EXPR; static_cast<void>(r); })
struct object
{
/* No state needed - this module is testing valptridx, not the type
* managed by valptridx.
*/
};
using objnum_t = uint16_t;
constexpr std::integral_constant<std::size_t, 350> MAX_OBJECTS{};
DXX_VALPTRIDX_DECLARE_SUBTYPE(, object, objnum_t, MAX_OBJECTS);
DXX_VALPTRIDX_DEFINE_SUBTYPE_TYPEDEFS(object, obj);
DXX_VALPTRIDX_DEFINE_GLOBAL_FACTORIES(object, obj, Objects);
valptridx<object>::array_managed_type Objects;
/* Some valptridx names are normally protected. The tests need access
* to them. Define a subclass that relaxes the permissions.
*/
struct valptridx_access_override : valptridx<object>
{
using base_type = valptridx<object>;
using typename base_type::allow_end_construction;
using typename base_type::index_range_exception;
using typename base_type::null_pointer_exception;
using typename base_type::report_error_uses_exception;
};
/* Test that using the factory does not throw an exception on the
* highest valid index.
*/
BOOST_AUTO_TEST_CASE(idx_no_exception_near_end)
{
BOOST_CHECK_NO_THROW(
DXX_TEST_VALPTRIDX_IGNORE_RETURN(
Objects.vmptr(optimizer_hidden_variable<objnum_t>(MAX_OBJECTS - 1)))
);
}
/* Test that using the factory does throw an exception on the lowest
* invalid index and on the highest representable integer.
*/
BOOST_AUTO_TEST_CASE(idx_exception_at_end)
{
if (!valptridx_access_override::report_error_uses_exception::value)
/* If valptridx is configured not to use exceptions to report
* errors, these tests will fail even when the logic is correct.
* Skip them in that case.
*/
return;
BOOST_CHECK_THROW(
DXX_TEST_VALPTRIDX_IGNORE_RETURN(
Objects.vmptr(
optimizer_hidden_variable<objnum_t>(MAX_OBJECTS))),
valptridx_access_override::index_range_exception);
BOOST_CHECK_THROW(
DXX_TEST_VALPTRIDX_IGNORE_RETURN(
Objects.vmptr(
optimizer_hidden_variable<objnum_t>(UINT16_MAX))),
valptridx_access_override::index_range_exception);
}
BOOST_AUTO_TEST_CASE(iter_no_exception_at_allowed_end)
{
/* Test the allowed side of end() handling for off-by-one bugs.
*/
BOOST_CHECK_NO_THROW(DXX_TEST_VALPTRIDX_IGNORE_RETURN(
(Objects.set_count(optimizer_hidden_variable<objnum_t>(MAX_OBJECTS - 1)), Objects.vmptr.end())
));
BOOST_CHECK_NO_THROW(DXX_TEST_VALPTRIDX_IGNORE_RETURN(
(Objects.set_count(optimizer_hidden_variable<objnum_t>(MAX_OBJECTS)), Objects.vmptr.end())
));
}
BOOST_AUTO_TEST_CASE(iter_exception_if_beyond_end)
{
/* Test that end() throws an exception if the range's count exceeds
* the valid storage size.
*/
if (!valptridx_access_override::report_error_uses_exception::value)
return;
BOOST_CHECK_THROW((DXX_TEST_VALPTRIDX_IGNORE_RETURN(
(Objects.set_count(optimizer_hidden_variable<objnum_t>(MAX_OBJECTS + 1)), Objects.vmptr.end())
)), valptridx_access_override::index_range_exception);
}
BOOST_AUTO_TEST_CASE(guarded_in_range)
{
auto &&g = Objects.vmptr.check_untrusted(optimizer_hidden_variable<objnum_t>(0));
optimizer_hidden_variable(g);
/* Test that a `guarded` returned by `check_untrusted` on a valid
* index throws if accessed without testing its validity. A runtime
* check on validity is mandatory.
*
* These exceptions are not configurable, so no test on
* `report_error_uses_exception` is required.
*/
BOOST_CHECK_THROW(DXX_TEST_VALPTRIDX_IGNORE_RETURN(*g), std::logic_error);
/* Test its validity */
BOOST_TEST(static_cast<bool>(g));
/* Test that it can now be read successfully. */
BOOST_CHECK_NO_THROW(DXX_TEST_VALPTRIDX_IGNORE_RETURN(*g));
}
BOOST_AUTO_TEST_CASE(guarded_out_of_range)
{
auto &&g = Objects.vmptr.check_untrusted(optimizer_hidden_variable<objnum_t>(MAX_OBJECTS));
optimizer_hidden_variable(g);
/* Test that a `guarded` returned by `check_untrusted` on an invalid
* index throws if accessed without testing its validity.
*
* Test its validity.
*
* Test that it still throws afterward.
*/
BOOST_CHECK_THROW(DXX_TEST_VALPTRIDX_IGNORE_RETURN(*g), std::logic_error);
BOOST_TEST(!static_cast<bool>(g));
BOOST_CHECK_THROW(DXX_TEST_VALPTRIDX_IGNORE_RETURN(*g), std::logic_error);
}
BOOST_AUTO_TEST_CASE(idx_convert_check)
{
using vo = valptridx<object>;
BOOST_CHECK_NO_THROW(
DXX_TEST_VALPTRIDX_IGNORE_RETURN(({
typename vo::imidx i(static_cast<objnum_t>(0));
typename vo::vmidx{i};
}))
);
typename vo::imidx i_none0(static_cast<objnum_t>(~0));
auto i_none = optimizer_hidden_variable(i_none0);
if (!valptridx_access_override::report_error_uses_exception::value)
return;
BOOST_CHECK_THROW(
DXX_TEST_VALPTRIDX_IGNORE_RETURN(({
typename vo::vmidx{i_none};
})), valptridx_access_override::index_range_exception
);
BOOST_CHECK_THROW(
DXX_TEST_VALPTRIDX_IGNORE_RETURN(({
typename vo::vmidx{optimizer_hidden_variable<objnum_t>(~0)};
})), valptridx_access_override::index_range_exception
);
}
BOOST_AUTO_TEST_CASE(ptr_convert_check)
{
using vo = valptridx<object>;
typename vo::imptr i_none0(nullptr);
auto i_none = optimizer_hidden_variable(i_none0);
auto &&i_zero = Objects.imptr(optimizer_hidden_variable<objnum_t>(0));
BOOST_CHECK_NO_THROW(
DXX_TEST_VALPTRIDX_IGNORE_RETURN(({
typename vo::vmptr v_zero{i_zero};
v_zero.get_nonnull_pointer();
}))
);
if (!valptridx_access_override::report_error_uses_exception::value)
return;
BOOST_CHECK_THROW(
DXX_TEST_VALPTRIDX_IGNORE_RETURN(({
typename vo::vmptr{i_none};
})), valptridx_access_override::null_pointer_exception
);
BOOST_CHECK_THROW(
DXX_TEST_VALPTRIDX_IGNORE_RETURN(({
Objects.vmptr(optimizer_hidden_variable<object *>(nullptr));
})), valptridx_access_override::null_pointer_exception
);
BOOST_CHECK_THROW(
DXX_TEST_VALPTRIDX_IGNORE_RETURN(({
i_none.get_nonnull_pointer();
})), valptridx_access_override::null_pointer_exception
);
auto &&pi_zero = Objects.vmptridx(optimizer_hidden_variable<objnum_t>(0));
BOOST_CHECK_THROW(
DXX_TEST_VALPTRIDX_IGNORE_RETURN(({
pi_zero.absolute_sibling(optimizer_hidden_variable<objnum_t>(Objects.size()));
})), valptridx_access_override::index_range_exception
);
}
/* For each style selector, set a type. Test that the type is used.
* Reset to a different type. Test that the second type is used. Clear
* the selector, so that any further use is an error.
*
* This combination verifies that the requested style is set, and that
* the match is not an accident. If the first test succeeded because
* the style happened to match for the wrong reason, then redefining the
* style would have no effect, and the second test would fail.
*/
/* Test DXX_VALPTRIDX_REPORT_ERROR_STYLE_default - the final fallback,
* used only when everything else is unset.
*/
#undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_default
#define DXX_VALPTRIDX_REPORT_ERROR_STYLE_default exception
static_assert(valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(const, error_report_test)>::value == valptridx_detail::untyped_utilities::report_error_style::exception);
static_assert(valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(mutable, error_report_test)>::value == valptridx_detail::untyped_utilities::report_error_style::exception);
#undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_default
#define DXX_VALPTRIDX_REPORT_ERROR_STYLE_default undefined
static_assert(valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(const, error_report_test)>::value == valptridx_detail::untyped_utilities::report_error_style::undefined);
static_assert(valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(mutable, error_report_test)>::value == valptridx_detail::untyped_utilities::report_error_style::undefined);
#undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_default
#define DXX_VALPTRIDX_REPORT_ERROR_STYLE_default /* invalid mapping - force an error on use */
/* Test the const/mutable defaults. These are the second lowest
* priority, above only DXX_VALPTRIDX_REPORT_ERROR_STYLE_default.
*/
#undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_default
#define DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_default trap_terse
#undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_default
#define DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_default trap_verbose
static_assert(valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(const, error_report_test)>::value == valptridx_detail::untyped_utilities::report_error_style::trap_terse);
static_assert(valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(mutable, error_report_test)>::value == valptridx_detail::untyped_utilities::report_error_style::trap_verbose);
#undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_default
#define DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_default exception
#undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_default
#define DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_default trap_terse
static_assert(valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(const, error_report_test)>::value == valptridx_detail::untyped_utilities::report_error_style::exception);
static_assert(valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(mutable, error_report_test)>::value == valptridx_detail::untyped_utilities::report_error_style::trap_terse);
#undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_default
#define DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_default
#undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_default
#define DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_default
/* Test the type-specific default. This is the third lowest priority.
*/
#define DXX_VALPTRIDX_REPORT_ERROR_STYLE_default_error_report_test undefined
static_assert(valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(const, error_report_test)>::value == valptridx_detail::untyped_utilities::report_error_style::undefined);
static_assert(valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(mutable, error_report_test)>::value == valptridx_detail::untyped_utilities::report_error_style::undefined);
#undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_default_error_report_test
#define DXX_VALPTRIDX_REPORT_ERROR_STYLE_default_error_report_test exception
static_assert(valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(const, error_report_test)>::value == valptridx_detail::untyped_utilities::report_error_style::exception);
static_assert(valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(mutable, error_report_test)>::value == valptridx_detail::untyped_utilities::report_error_style::exception);
#undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_default_error_report_test
#define DXX_VALPTRIDX_REPORT_ERROR_STYLE_default_error_report_test
/* Test the const/mutable type-specific. These are the highest
* priority.
*/
#define DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_error_report_test trap_terse
#define DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_error_report_test trap_verbose
static_assert(valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(const, error_report_test)>::value == valptridx_detail::untyped_utilities::report_error_style::trap_terse);
static_assert(valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(mutable, error_report_test)>::value == valptridx_detail::untyped_utilities::report_error_style::trap_verbose);
#undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_error_report_test
#undef DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_error_report_test
#define DXX_VALPTRIDX_REPORT_ERROR_STYLE_const_error_report_test exception
#define DXX_VALPTRIDX_REPORT_ERROR_STYLE_mutable_error_report_test undefined
static_assert(valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(const, error_report_test)>::value == valptridx_detail::untyped_utilities::report_error_style::exception);
static_assert(valptridx_detail::untyped_utilities::error_style_dispatch<DXX_VALPTRIDX_ERROR_STYLE_DISPATCH_PARAMETERS(mutable, error_report_test)>::value == valptridx_detail::untyped_utilities::report_error_style::undefined);