CoolProp 8.0.0
An open-source fluid property and humid air property database
CoolProp-Tests-FPUGuard.cpp
Go to the documentation of this file.
1// Regression tests for CoolProp::fpu_guard (GitHub #3012 / bd CoolProp-i3o).
2//
3// CoolProp uses NaN and _HUGE as in-band sentinels. Host environments that run
4// with FP exceptions *unmasked* (Delphi, some TRNSYS/Fortran builds) take a
5// hardware trap / SIGFPE the instant CoolProp touches one of those values,
6// crashing mid-calculation. fpu_guard masks FP exceptions on construction and
7// restores the caller's environment on destruction.
8//
9// These tests assert the guard's contract directly (it is a header-only type,
10// so it is reachable from the Catch2 runner even though CoolPropLib.cpp -- the
11// C-interface that wires the guard into every entry point -- is excluded from
12// that binary by CMakeLists.txt). The decisive check is the divide-and-invalid
13// inside the guarded scope: with FE_INVALID *enabled* and no masking, the
14// volatile 0.0/0.0 below would raise SIGFPE and abort the process.
15
16#if defined(ENABLE_CATCH)
17
18# include <catch2/catch_all.hpp>
19# include "CoolProp/FPUGuard.h"
20# include <cmath> // std::isnan
21# include <cfenv> // std::fetestexcept / std::feclearexcept
22
23// fegetexcept/feenableexcept/fedisableexcept are GNU glibc extensions; the
24// "trapping host" half of the contract can only be exercised where they exist.
25# if defined(__GLIBC__) && defined(FE_ALL_EXCEPT)
26
27TEST_CASE("fpu_guard masks FP traps inside its scope and survives a NaN op", "[fpu_guard][fpu][3012]") {
28 std::feclearexcept(FE_ALL_EXCEPT);
29 feenableexcept(FE_INVALID); // emulate a trapping host (Delphi/TRNSYS)
30
31 {
33 // Inside the guard FE_INVALID must be masked, so this 0.0/0.0 (which a
34 // trapping host would fault on) produces a quiet NaN instead of SIGFPE.
35 CHECK((fegetexcept() & FE_INVALID) == 0);
36 volatile double zero = 0.0;
37 volatile double nan = zero / zero;
38 CHECK(std::isnan(nan));
39 }
40
41 // After the guard the caller's trapping configuration is restored ...
42 CHECK((fegetexcept() & FE_INVALID) != 0);
43 // ... and the status flags the guard raised are cleared, so a polling host
44 // (Excel/VBA) sees a clean status word.
45 CHECK(std::fetestexcept(FE_INVALID) == 0);
46
47 fedisableexcept(FE_ALL_EXCEPT);
48 std::feclearexcept(FE_ALL_EXCEPT);
49}
50
51TEST_CASE("fpu_guard leaves an already-masked environment unchanged", "[fpu_guard][fpu][3012]") {
52 fedisableexcept(FE_ALL_EXCEPT); // nothing trapping (the common host default)
53 std::feclearexcept(FE_ALL_EXCEPT);
54
55 {
57 CHECK((fegetexcept() & FE_ALL_EXCEPT) == 0);
58 }
59
60 // Restoring an empty trapping set must not enable anything spuriously.
61 CHECK((fegetexcept() & FE_ALL_EXCEPT) == 0);
62 std::feclearexcept(FE_ALL_EXCEPT);
63}
64# endif // __GLIBC__ && FE_ALL_EXCEPT
65
66TEST_CASE("fpu_guard is constructible and clears status flags on every platform", "[fpu_guard][fpu][3012]") {
67 // Platform-independent smoke test: the guard must compile, run a NaN op
68 // without aborting even where the GNU trapping API is unavailable, and -- on
69 // every platform path (glibc/MSVC/macOS) -- leave a clean status word once
70 // destroyed, since each ~fpu_guard clears the flags it raised.
71 std::feclearexcept(FE_ALL_EXCEPT);
72 {
74 volatile double zero = 0.0;
75 volatile double nan = zero / zero; // raises FE_INVALID inside the guard
76 CHECK(std::isnan(nan));
77 }
78 // The guard is now destroyed; the flag it raised must have been cleared.
79 CHECK(std::fetestexcept(FE_INVALID) == 0);
80}
81
82#endif // ENABLE_CATCH