diff --git a/libc/test/src/CMakeLists.txt b/libc/test/src/CMakeLists.txt --- a/libc/test/src/CMakeLists.txt +++ b/libc/test/src/CMakeLists.txt @@ -1,3 +1,30 @@ +function(add_fp_unittest name) + cmake_parse_arguments( + "MATH_UNITTEST" + "NEED_MPFR" # Optional arguments + "" # Single value arguments + "" # Multi-value arguments + ${ARGN} + ) + + if(MATH_UNITTEST_NEED_MPFR) + if(NOT LIBC_TESTS_CAN_USE_MPFR) + message("WARNING: Math test ${name} will be skipped as MPFR library is not available.") + return() + endif() + endif() + + add_libc_unittest(${name} ${MATH_UNITTEST_UNPARSED_ARGUMENTS}) + get_fq_target_name(${name} fq_target_name) + if (NOT TARGET ${fq_target_name}) + return() + endif() + if(MATH_UNITTEST_NEED_MPFR) + target_link_libraries(${fq_target_name} PRIVATE libcMPFRWrapper -lmpfr -lgmp) + endif() + target_link_libraries(${fq_target_name} PRIVATE LibcFPTestHelpers) +endfunction(add_fp_unittest) + add_subdirectory(__support) add_subdirectory(ctype) add_subdirectory(errno) diff --git a/libc/test/src/fenv/CMakeLists.txt b/libc/test/src/fenv/CMakeLists.txt --- a/libc/test/src/fenv/CMakeLists.txt +++ b/libc/test/src/fenv/CMakeLists.txt @@ -74,7 +74,7 @@ if (NOT LLVM_USE_SANITIZER) # Sanitizers don't like SIGFPE. So, we will run the # tests which raise SIGFPE only in non-sanitizer builds. - add_libc_unittest( + add_fp_unittest( enabled_exceptions_test SUITE libc_fenv_unittests @@ -88,14 +88,14 @@ libc.utils.FPUtil.fputil ) - add_libc_unittest( + add_fp_unittest( feholdexcept_test SUITE libc_fenv_unittests SRCS feholdexcept_test.cpp DEPENDS - libc.include.signal + libc.include.fenv libc.src.fenv.feholdexcept libc.utils.FPUtil.fputil ) diff --git a/libc/test/src/fenv/enabled_exceptions_test.cpp b/libc/test/src/fenv/enabled_exceptions_test.cpp --- a/libc/test/src/fenv/enabled_exceptions_test.cpp +++ b/libc/test/src/fenv/enabled_exceptions_test.cpp @@ -11,6 +11,7 @@ #include "src/fenv/fetestexcept.h" #include "utils/FPUtil/FEnv.h" +#include "utils/FPUtil/TestHelpers.h" #include "utils/UnitTest/Test.h" #include @@ -34,23 +35,16 @@ FE_DIVBYZERO | FE_INVALID | FE_INEXACT | FE_OVERFLOW | FE_UNDERFLOW; for (int e : excepts) { - ASSERT_DEATH( - [=] { - __llvm_libc::fputil::disableExcept(FE_ALL_EXCEPT); - __llvm_libc::fputil::enableExcept(e); - ASSERT_EQ(__llvm_libc::feclearexcept(FE_ALL_EXCEPT), 0); - // Raising all exceptions except |e| should not call the - // SIGFPE handler. They should set the exception flag though, - // so we verify that. - int others = allExcepts & ~e; - ASSERT_EQ(__llvm_libc::feraiseexcept(others), 0); - ASSERT_EQ(__llvm_libc::fetestexcept(others), others); - - // We don't check the return value when raising |e| as - // feraiseexcept will not return when it raises an enabled - // exception. - __llvm_libc::feraiseexcept(e); - }, - WITH_SIGNAL(SIGFPE)); + __llvm_libc::fputil::disableExcept(FE_ALL_EXCEPT); + __llvm_libc::fputil::enableExcept(e); + ASSERT_EQ(__llvm_libc::feclearexcept(FE_ALL_EXCEPT), 0); + // Raising all exceptions except |e| should not call the + // SIGFPE handler. They should set the exception flag though, + // so we verify that. + int others = allExcepts & ~e; + ASSERT_EQ(__llvm_libc::feraiseexcept(others), 0); + ASSERT_EQ(__llvm_libc::fetestexcept(others), others); + + ASSERT_RAISES_FP_EXCEPT([=] { __llvm_libc::feraiseexcept(e); }); } } diff --git a/libc/test/src/fenv/feholdexcept_test.cpp b/libc/test/src/fenv/feholdexcept_test.cpp --- a/libc/test/src/fenv/feholdexcept_test.cpp +++ b/libc/test/src/fenv/feholdexcept_test.cpp @@ -9,10 +9,10 @@ #include "src/fenv/feholdexcept.h" #include "utils/FPUtil/FEnv.h" +#include "utils/FPUtil/TestHelpers.h" #include "utils/UnitTest/Test.h" #include -#include TEST(LlvmLibcFEnvTest, RaiseAndCrash) { int excepts[] = {FE_DIVBYZERO, FE_INVALID, FE_INEXACT, FE_OVERFLOW, @@ -31,7 +31,6 @@ // When we put back the saved env which has the exception enabled, it // should crash with SIGFPE. __llvm_libc::fputil::setEnv(&env); - ASSERT_DEATH([=] { __llvm_libc::fputil::raiseExcept(e); }, - WITH_SIGNAL(SIGFPE)); + ASSERT_RAISES_FP_EXCEPT([=] { __llvm_libc::fputil::raiseExcept(e); }); } } diff --git a/libc/test/src/math/CMakeLists.txt b/libc/test/src/math/CMakeLists.txt --- a/libc/test/src/math/CMakeLists.txt +++ b/libc/test/src/math/CMakeLists.txt @@ -1,32 +1,5 @@ add_libc_testsuite(libc_math_unittests) -function(add_fp_unittest name) - cmake_parse_arguments( - "MATH_UNITTEST" - "NEED_MPFR" # Optional arguments - "" # Single value arguments - "" # Multi-value arguments - ${ARGN} - ) - - if(MATH_UNITTEST_NEED_MPFR) - if(NOT LIBC_TESTS_CAN_USE_MPFR) - message("WARNING: Math test ${name} will be skipped as MPFR library is not available.") - return() - endif() - endif() - - add_libc_unittest(${name} ${MATH_UNITTEST_UNPARSED_ARGUMENTS}) - get_fq_target_name(${name} fq_target_name) - if (NOT TARGET ${fq_target_name}) - return() - endif() - if(MATH_UNITTEST_NEED_MPFR) - target_link_libraries(${fq_target_name} PRIVATE libcMPFRWrapper -lmpfr -lgmp) - endif() - target_link_libraries(${fq_target_name} PRIVATE LibcFPTestHelpers) -endfunction(add_fp_unittest) - add_fp_unittest( cosf_test NEED_MPFR diff --git a/libc/utils/FPUtil/TestHelpers.h b/libc/utils/FPUtil/TestHelpers.h --- a/libc/utils/FPUtil/TestHelpers.h +++ b/libc/utils/FPUtil/TestHelpers.h @@ -61,6 +61,39 @@ return FPMatcher(expectedValue); } +// TODO: Make the matcher match specific exceptions instead of just identifying +// that an exception was raised. +class FPExceptMatcher : public __llvm_libc::testing::Matcher { + bool exceptionRaised; + +public: + class FunctionCaller { + public: + virtual ~FunctionCaller(){}; + virtual void call() = 0; + }; + + template static FunctionCaller *getFunctionCaller(Func func) { + struct Callable : public FunctionCaller { + Func f; + explicit Callable(Func theFunc) : f(theFunc) {} + void call() override { f(); } + }; + + return new Callable(func); + } + + // Takes ownership of func. + explicit FPExceptMatcher(FunctionCaller *func); + + bool match(bool unused) { return exceptionRaised; } + + void explainError(testutils::StreamWrapper &stream) override { + stream << "A floating point exception should have been raised but it " + << "wasn't\n"; + } +}; + } // namespace testing } // namespace fputil } // namespace __llvm_libc @@ -98,4 +131,15 @@ __llvm_libc::fputil::testing::getMatcher<__llvm_libc::testing::Cond_NE>( \ expected)) +#ifdef LLVM_LIBC_TEST_USE_FUCHSIA +#define ASSERT_RAISES_FP_EXCEPT(func) ASSERT_DEATH(func, WITH_SIGNAL(SIGFPE)) +#else +#define ASSERT_RAISES_FP_EXCEPT(func) \ + ASSERT_THAT( \ + true, \ + __llvm_libc::fputil::testing::FPExceptMatcher( \ + __llvm_libc::fputil::testing::FPExceptMatcher::getFunctionCaller( \ + func))) +#endif // LLVM_LIBC_TEST_USE_FUCHSIA + #endif // LLVM_LIBC_UTILS_FPUTIL_TEST_HELPERS_H diff --git a/libc/utils/FPUtil/TestHelpers.cpp b/libc/utils/FPUtil/TestHelpers.cpp --- a/libc/utils/FPUtil/TestHelpers.cpp +++ b/libc/utils/FPUtil/TestHelpers.cpp @@ -8,8 +8,12 @@ #include "TestHelpers.h" +#include "FEnv.h" #include "FPBits.h" +#include +#include +#include #include namespace __llvm_libc { @@ -70,6 +74,36 @@ template void describeValue(const char *, long double, testutils::StreamWrapper &); +#if defined(__WIN32) || defined(_WIN64) +#define sigjmp_buf jmp_buf +#define sigsetjmp(buf, save) setjmp(buf) +#define siglongjmp(buf) longjmp(buf) +#endif + +static thread_local sigjmp_buf jumpBuffer; +static thread_local bool caughtExcept; + +static void sigfpeHandler(int sig) { + caughtExcept = true; + siglongjmp(jumpBuffer, -1); +} + +FPExceptMatcher::FPExceptMatcher(FunctionCaller *func) { + auto oldSIGFPEHandler = signal(SIGFPE, &sigfpeHandler); + std::unique_ptr funcUP(func); + + caughtExcept = false; + fenv_t oldEnv; + fegetenv(&oldEnv); + if (sigsetjmp(jumpBuffer, 1) == 0) + funcUP->call(); + // We restore the previous floating point environment after + // the call to the function which can potentially raise SIGFPE. + fesetenv(&oldEnv); + signal(SIGFPE, oldSIGFPEHandler); + exceptionRaised = caughtExcept; +} + } // namespace testing } // namespace fputil } // namespace __llvm_libc