libcruft-util/test/debug/fpe.cpp

121 lines
3.7 KiB
C++

#include <cruft/util/tap.hpp>
#include <cruft/util/debug/fpe.hpp>
#include <cruft/util/debug/compiler.hpp>
#include <thread>
#include <csignal>
#include <csetjmp>
///////////////////////////////////////////////////////////////////////////////
static int volatile fpe_value = 0;
static sigjmp_buf fpe_jump;
//-----------------------------------------------------------------------------
static void handle_fpe (int, siginfo_t*, void *)
{
fpe_value = 1;
siglongjmp (fpe_jump, 1);
}
///////////////////////////////////////////////////////////////////////////////
float volatile nan_val = NAN;
float volatile inf_val = INFINITY;
float volatile zero_val = 0.f;
//-----------------------------------------------------------------------------
int main ()
{
struct sigaction fpe_sigaction {};
fpe_sigaction.sa_flags = SA_RESTART;
fpe_sigaction.sa_sigaction = handle_fpe;
sigaction (SIGFPE, &fpe_sigaction, nullptr);
cruft::TAP::logger tap;
// Test that we don't receive any exceptions by default.
fpe_value = 0;
cruft::debug::escape (inf_val - inf_val);
tap.expect_eq (fpe_value, 0, "FE_INVALID is disabled by default");
fpe_value = 0;
cruft::debug::escape (1.f / zero_val);
tap.expect_eq (fpe_value, 0, "FE_DIVBYZERO is disabled by default");
// After the signal handler exits we'll immediate re-enter the handler as
// the faulting floating ops are re-executed.
//
// Ideally we'd modify the FPU state, but we can't access that from the
// handler.
//
// So we longjmp out of the handler and use a conditional to avoid
// re-executing.
//
// However this approach doesn't restore the FPU state (I think; it doesn't
// restore _something_) and so the exceptions are permanently disabled.
//
// But threads get their own state. So.. we spin up a thread for each
// test. It's nasty, but:
// * it works
// * I'm not familiar enough with the area to do it a cleaner way
// * I've spent too much time on just writing this test already.
std::thread ([] () {
cruft::debug::fpe::enable ();
fpe_value = 0;
if (!sigsetjmp (fpe_jump, 1))
cruft::debug::escape (inf_val - inf_val);
}).join ();
tap.expect_eq (fpe_value, 1, "FE_INVALID is enabled after request");
std::thread ([] () {
cruft::debug::fpe::enable ();
fpe_value = 0;
if (!sigsetjmp (fpe_jump, 1))
cruft::debug::escape (1.f / zero_val);
}).join ();
tap.expect_eq (fpe_value, 1, "FE_DIVBYZERO is enabled after request");
std::thread ([] () {
fpe_value = 0;
cruft::debug::fpe::scoped_reset resetter;
if (!sigsetjmp (fpe_jump, 1))
cruft::debug::escape (inf_val - inf_val);
}).join ();
tap.expect_eq (fpe_value, 0, "scoped_reset enables exceptions");
std::thread ([] () {
fpe_value = 0;
// Do a test beforehand just in case we've forgotten to reset the
// fpe_value variable properly.
if (!sigsetjmp (fpe_jump, 1))
cruft::debug::escape (inf_val - inf_val);
{
cruft::debug::fpe::scoped_reset resetter;
}
if (!sigsetjmp (fpe_jump, 1))
cruft::debug::escape (inf_val - inf_val);
}).join ();
tap.expect_eq (fpe_value, 0, "scoped_reset disables exceptions");
// Ensure that the scoped object actually disables state if it's
// previously been statically set.
std::thread ([] {
fpe_value = 0;
cruft::debug::fpe::enable ();
cruft::debug::fpe::scoped_reset resetter (false);
if (!sigsetjmp (fpe_jump, 1))
cruft::debug::escape (inf_val - inf_val);
}).join ();
return tap.status ();
}