dxx-rebirth/common/unittest/valptridx-range.cpp
Kp 0b070880a7 Use C++11 user-defined literals to dispatch valptridx error style
Switch valptridx error style dispatching from using macro pasting to
using C++11 user-defined literals.  This makes the code a bit easier to
read, and removes the need for a C99-conforming preprocessor here, which
should help anyone trying to port to Microsoft Visual Studio.

The new implementation also fixes a limitation of the previous
implementation.  Before, an override that referenced an invalid name
could be silently ignored.  Now, incorrect overrides cause an attempt to
use an undefined instantiation, which fails with a compilation error.
2020-04-26 17:26:23 +00:00

311 lines
14 KiB
C++

#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_none(static_cast<objnum_t>(~0));
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_none(nullptr);
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);