2 * @brief a test suite engine
4 /* Copyright 1999,2000,2001 BrightStation PLC
5 * Copyright 2002 Ananova Ltd
6 * Copyright 2002-2024 Olly Betts
7 * Copyright 2007 Richard Boulton
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of the
12 * License, or (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
27 #include "testsuite.h"
30 # include "backendmanager.h"
32 #include "fdtracker.h"
33 #include "testrunner.h"
34 #include "safeunistd.h"
37 # include <valgrind/memcheck.h>
38 # include <sys/types.h>
39 # include "safefcntl.h"
49 #include <cfloat> // For DBL_DIG.
50 #include <cmath> // For ceil, fabs, log10.
55 #include "gnu_getopt.h"
70 # include <xapian/error.h>
72 #include "errno_to_string.h"
73 #include "filetests.h"
75 #include "stringutils.h"
79 /// The global verbose flag.
83 static int vg_log_fd
= -1;
86 #if HAVE_DECL_SIGSETJMP && HAVE_DECL_SIGLONGJMP
87 # define SIGSETJMP(ENV, SAVESIGS) sigsetjmp(ENV, SAVESIGS)
88 # define SIGLONGJMP(ENV, VAL) siglongjmp(ENV, VAL)
89 # define SIGJMP_BUF sigjmp_buf
91 # define SIGSETJMP(ENV, SAVESIGS) setjmp(ENV)
92 # define SIGLONGJMP(ENV, VAL) longjmp(ENV, VAL)
93 # define SIGJMP_BUF jmp_buf
96 /// The exception type we were expecting in TEST_EXCEPTION
97 // We use this to attempt to diagnose when the code fails to catch an
98 // exception when it should (due to a compiler or runtime fault in
100 const char * expected_exception
= NULL
;
102 const char* expected_failure
;
104 /// The debug printing stream
105 std::ostringstream tout
;
107 int test_driver::runs
= 0;
108 test_driver::result
test_driver::subtotal
;
109 test_driver::result
test_driver::total
;
110 string
test_driver::argv0
;
111 string
test_driver::opt_help
;
112 map
<int, string
*> test_driver::short_opts
;
113 vector
<string
> test_driver::test_names
;
114 bool test_driver::abort_on_error
= false;
115 string
test_driver::col_red
, test_driver::col_green
;
116 string
test_driver::col_yellow
, test_driver::col_reset
;
117 bool test_driver::use_cr
= false;
119 // time constant in seconds to mark tests as slow or not
120 static const double SLOW_TEST_THRESHOLD
= 10.00;
122 // vector to store the slow tests
123 static vector
<pair
<const char*, double>> slow_tests
;
126 test_driver::write_and_clear_tout()
128 const string
& s
= tout
.str();
136 test_driver::get_srcdir()
138 char *p
= getenv("srcdir");
139 if (p
!= NULL
) return string(p
);
141 // Default srcdir to the pathname of argv[0].
142 string
srcdir(argv0
);
143 string::size_type i
= srcdir
.find_last_of(DIR_SEPS
);
145 if (i
!= string::npos
) {
146 srcfile
.assign(srcdir
, i
+ 1, string::npos
);
148 // libtool may put the real executable in .libs.
149 i
= srcdir
.find_last_of(DIR_SEPS
);
150 if (srcdir
.substr(i
+ 1) == ".libs") {
152 // And it may have an "lt-" prefix.
153 if (startswith(srcfile
, "lt-")) srcfile
.erase(0, 3);
156 // No path of argv[0], so default srcdir to the current directory.
157 // This may not work if libtool is involved as the true executable is
158 // sometimes in ".libs".
163 // Remove any trailing ".exe" suffix, since some platforms add this.
164 if (endswith(srcfile
, ".exe")) srcfile
.resize(srcfile
.size() - 4);
167 if (!file_exists(srcdir
+ '/' + srcfile
+ ".cc")) {
169 << ": srcdir is not in the environment and I can't guess it!\n"
170 "Run test programs using the runtest script - see HACKING "
177 test_driver::test_driver(const test_desc
*tests_
)
178 : out(cout
.rdbuf()), tests(tests_
)
183 static SIGJMP_BUF jb
;
184 static int signum
= 0;
185 static void * sigaddr
= NULL
;
187 // Needs C linkage so we can pass it to sigaction()/signal() without problems.
190 #if defined HAVE_SIGACTION && defined SA_SIGINFO
192 static void handle_sig(int signum_
, siginfo_t
*si
, void *)
194 // Disable all our signal handlers to avoid problems if the signal
195 // handling code causes a signal.
197 sa
.sa_handler
= SIG_DFL
;
198 sigemptyset(&sa
.sa_mask
);
200 // We set the handlers with SA_RESETHAND, but that will only reset the
201 // handler for the signal which fired.
202 if (signum_
!= SIGSEGV
) sigaction(SIGSEGV
, &sa
, NULL
);
203 if (signum_
!= SIGFPE
) sigaction(SIGFPE
, &sa
, NULL
);
204 if (signum_
!= SIGILL
) sigaction(SIGILL
, &sa
, NULL
);
206 if (signum_
!= SIGBUS
) sigaction(SIGBUS
, &sa
, NULL
);
209 if (signum_
!= SIGPIPE
) sigaction(SIGPIPE
, &sa
, NULL
);
212 if (signum_
!= SIGSTKFLT
) sigaction(SIGSTKFLT
, &sa
, NULL
);
215 sigaddr
= si
->si_addr
;
222 static void handle_sig(int signum_
)
224 // Disable all our signal handlers to avoid problems if the signal
225 // handling code causes a signal.
226 signal(SIGSEGV
, SIG_DFL
);
227 signal(SIGFPE
, SIG_DFL
);
228 signal(SIGILL
, SIG_DFL
);
230 signal(SIGBUS
, SIG_DFL
);
233 signal(SIGPIPE
, SIG_DFL
);
236 signal(SIGSTKFLT
, SIG_DFL
);
245 class SignalRedirector
{
249 SignalRedirector() : active(false) { }
254 // SA_SIGINFO is not universal (e.g. not present on Linux < 2.2 or
255 // older Hurd). If we have it, we use it to report the address
256 // associated with the signal (for signals where that makes sense).
257 #if defined HAVE_SIGACTION && defined SA_SIGINFO
259 sa
.sa_sigaction
= handle_sig
;
260 sigemptyset(&sa
.sa_mask
);
261 sa
.sa_flags
= SA_RESETHAND
|SA_SIGINFO
;
262 sigaction(SIGSEGV
, &sa
, NULL
);
263 sigaction(SIGFPE
, &sa
, NULL
);
264 sigaction(SIGILL
, &sa
, NULL
);
266 sigaction(SIGBUS
, &sa
, NULL
);
269 sigaction(SIGPIPE
, &sa
, NULL
);
272 sigaction(SIGSTKFLT
, &sa
, NULL
);
275 signal(SIGSEGV
, handle_sig
);
276 signal(SIGFPE
, handle_sig
);
277 signal(SIGILL
, handle_sig
);
279 signal(SIGBUS
, handle_sig
);
282 signal(SIGPIPE
, handle_sig
);
285 signal(SIGSTKFLT
, handle_sig
);
289 ~SignalRedirector() {
291 #if defined HAVE_SIGACTION && defined SA_SIGINFO
293 sa
.sa_handler
= SIG_DFL
;
294 sigemptyset(&sa
.sa_mask
);
296 sigaction(SIGSEGV
, &sa
, NULL
);
297 sigaction(SIGFPE
, &sa
, NULL
);
298 sigaction(SIGILL
, &sa
, NULL
);
300 sigaction(SIGBUS
, &sa
, NULL
);
303 sigaction(SIGPIPE
, &sa
, NULL
);
306 sigaction(SIGSTKFLT
, &sa
, NULL
);
309 signal(SIGSEGV
, SIG_DFL
);
310 signal(SIGFPE
, SIG_DFL
);
311 signal(SIGILL
, SIG_DFL
);
313 signal(SIGBUS
, SIG_DFL
);
316 signal(SIGPIPE
, SIG_DFL
);
319 signal(SIGSTKFLT
, SIG_DFL
);
326 // A wrapper around the tests to trap exceptions,
327 // and avoid having to catch them in every test function.
328 // If this test driver is used for anything other than
329 // Xapian tests, then this ought to be provided by
330 // the client, really.
331 // return: test_driver::PASS, test_driver::FAIL, test_driver::SKIP,
332 // test_driver::XFAIL or test_driver:XPASS.
333 test_driver::test_result
334 test_driver::runtest(const test_desc
*test
)
336 // This is used to make a note of how many times we've run the test
337 volatile int runcount
= 0;
345 // Suppress warning about _setjmp and C++ object destruction. It's not ideal
346 // that some destructors may not get called, but this is in the test harness
347 // rather than production code, and overall it's more helpful for the test
348 // harness to be able to report clearly which testcase triggered a signal.
349 # pragma warning(push)
350 # pragma warning(disable:4611)
352 if (SIGSETJMP(jb
, 1) == 0) {
354 # pragma warning(pop)
356 SignalRedirector sig
; // use object so signal handlers are reset
357 static bool catch_signals
=
358 (getenv("XAPIAN_TESTSUITE_SIG_DFL") == NULL
);
359 if (catch_signals
) sig
.activate();
361 expected_exception
= NULL
;
362 expected_failure
= NULL
;
365 long vg_leaks
= 0, vg_dubious
= 0, vg_reachable
= 0;
366 if (vg_log_fd
!= -1) {
367 VALGRIND_DO_LEAK_CHECK
;
368 vg_errs
= VALGRIND_COUNT_ERRORS
;
370 VALGRIND_COUNT_LEAKS(vg_leaks
, vg_dubious
, vg_reachable
, dummy
);
372 // Skip past any unread log output.
373 lseek(vg_log_fd
, 0, SEEK_END
);
378 write_and_clear_tout();
381 backendmanager
->clean_up();
384 if (vg_log_fd
!= -1) {
385 // We must empty tout before asking valgrind to perform its
386 // leak checks, otherwise the buffers holding the output
387 // may be identified as a memory leak (especially if >1K of
388 // output has been buffered it appears...)
390 #define REPORT_FAIL_VG(M) do { \
393 ssize_t c = read(vg_log_fd, buf, sizeof(buf)); \
394 if (c == 0 || (c < 0 && errno != EINTR)) break; \
395 if (c > 0) out << string(buf, c); \
398 out << " " << col_red << M << col_reset; \
400 // Record the current position so we can restore it so
401 // REPORT_FAIL_VG() gets the whole output.
402 off_t curpos
= lseek(vg_log_fd
, 0, SEEK_CUR
);
405 ssize_t c
= read(vg_log_fd
, buf
, sizeof(buf
));
406 if (c
== 0 || (c
< 0 && errno
!= EINTR
)) {
411 // Valgrind output has "==<pid>== \n" between
412 // report "records", so skip any lines like that,
413 // and also any warnings and continuation lines.
417 spc
= static_cast<const char *>(
418 memchr(buf
+ i
, ' ', c
- i
));
428 (memcmp(buf
+ i
, "Warning:", 8) == 0 ||
429 memcmp(buf
+ i
, " ", 3) == 0)) {
433 nl
= static_cast<const char *>(
434 memchr(buf
+ i
, '\n', c
- i
));
445 char *start
= buf
+ i
;
447 if (c
> 128) c
= 128;
451 p
= static_cast<const char*>(
452 memchr(start
, '\n', c
));
453 if (p
!= NULL
) c
= p
- start
;
456 memmove(buf
, start
, c
);
461 lseek(vg_log_fd
, curpos
, SEEK_SET
);
463 int vg_errs2
= VALGRIND_COUNT_ERRORS
;
464 vg_errs
= vg_errs2
- vg_errs
;
465 VALGRIND_DO_LEAK_CHECK
;
466 long vg_leaks2
= 0, vg_dubious2
= 0, vg_reachable2
= 0;
468 VALGRIND_COUNT_LEAKS(vg_leaks2
, vg_dubious2
, vg_reachable2
,
471 vg_leaks
= vg_leaks2
- vg_leaks
;
472 vg_dubious
= vg_dubious2
- vg_dubious
;
473 vg_reachable
= vg_reachable2
- vg_reachable
;
475 string
fail_msg(buf
);
476 if (fail_msg
.empty())
477 fail_msg
= "VALGRIND DETECTED A PROBLEM";
478 REPORT_FAIL_VG(fail_msg
);
482 REPORT_FAIL_VG("LEAKED " << vg_leaks
<< " BYTES");
485 if (vg_dubious
> 0) {
486 // If code deliberately holds onto blocks by a pointer
487 // not to the start (e.g. languages/utilities.c does)
488 // then we need to rerun the test to see if the leak is
491 out
<< col_yellow
<< " PROBABLY LEAKED MEMORY - RETRYING TEST" << col_reset
;
492 runcount
= runcount
+ 1;
493 // Ensure that any cached memory from fd tracking
494 // is allocated before we rerun the test.
495 (void)fdtracker
.check();
498 REPORT_FAIL_VG("PROBABLY LEAKED " << vg_dubious
<< " BYTES");
501 if (vg_reachable
> 0) {
502 // C++ STL implementations often "horde" released
503 // memory - the runtest script sets GLIBCXX_FORCE_NEW=1
504 // which under GCC will disable this behaviour and so
505 // we avoid this issue, but for other compilers this
509 // https://valgrind.org/docs/manual/faq.html#faq.reports
511 // For now, just use runcount to rerun the test and see
512 // if more is leaked - hopefully this shouldn't give
515 out
<< col_yellow
<< " POSSIBLE UNRELEASED MEMORY - RETRYING TEST" << col_reset
;
516 runcount
= runcount
+ 1;
517 // Ensure that any cached memory from fd tracking
518 // is allocated before we rerun the test.
519 (void)fdtracker
.check();
522 REPORT_FAIL_VG("FAILED TO RELEASE " << vg_reachable
<< " BYTES");
527 if (!fdtracker
.check()) {
529 out
<< col_yellow
<< " POSSIBLE FDLEAK:" << fdtracker
.get_message() << col_reset
;
530 runcount
= runcount
+ 1;
533 out
<< col_red
<< " FDLEAK:" << fdtracker
.get_message() << col_reset
;
536 } catch (const TestFail
&) {
538 if (expected_failure
) {
539 out
<< col_yellow
<< "XFAIL (" << expected_failure
<< ")";
541 out
<< col_red
<< "FAILED";
544 write_and_clear_tout();
545 return expected_failure
? XFAIL
: FAIL
;
546 } catch (const TestSkip
&) {
547 out
<< col_yellow
<< " SKIPPED" << col_reset
;
548 write_and_clear_tout();
551 } catch (const Xapian::Error
&err
) {
553 string errclass
= err
.get_type();
554 if (expected_exception
&& expected_exception
== errclass
) {
555 out
<< col_yellow
<< "C++ FAILED TO CATCH " << errclass
<< col_reset
;
558 if (errclass
== "NetworkError" &&
559 err
.get_error_string() != NULL
&&
560 err
.get_error_string() == errno_to_string(ECHILD
)) {
561 // ECHILD suggests we've run out of processes, and that's
562 // much more likely to be a system issue than a Xapian bug.
564 // We also see apparently spurious ECHILD on Debian
565 // buildds sometimes: https://bugs.debian.org/681941
566 out
<< col_yellow
<< "ECHILD in network code" << col_reset
;
570 if (expected_failure
) {
571 out
<< col_yellow
<< "XFAIL (" << expected_failure
574 out
<< col_red
<< "FAIL: ";
576 out
<< err
.get_description() << col_reset
;
577 write_and_clear_tout();
578 return expected_failure
? XFAIL
: FAIL
;
580 } catch (const string
& msg
) {
582 if (expected_failure
) {
583 out
<< col_yellow
<< "XFAIL (" << expected_failure
586 out
<< col_red
<< "FAIL: ";
588 out
<< "EXCEPTION std::string " << msg
<< col_reset
;
589 write_and_clear_tout();
590 return expected_failure
? XFAIL
: FAIL
;
591 } catch (const std::exception
& e
) {
593 if (expected_failure
) {
594 out
<< col_yellow
<< "XFAIL (" << expected_failure
597 out
<< col_red
<< "FAIL: ";
600 out
<< "std::exception";
602 const char * name
= typeid(e
).name();
603 # ifdef HAVE_CXXABI_H
604 // __cxa_demangle() apparently requires GCC >= 3.1.
605 // Demangle the name which GCC returns for type_info::name().
607 char * realname
= abi::__cxa_demangle(name
, NULL
, 0, &status
);
618 out
<< ": " << e
.what() << col_reset
;
619 write_and_clear_tout();
620 return expected_failure
? XFAIL
: FAIL
;
621 } catch (const char * msg
) {
623 if (expected_failure
) {
624 out
<< col_yellow
<< "XFAIL (" << expected_failure
627 out
<< col_red
<< "FAIL: ";
630 out
<< "EXCEPTION char* " << msg
;
632 out
<< "EXCEPTION (char*)NULL";
635 write_and_clear_tout();
636 return expected_failure
? XFAIL
: FAIL
;
639 if (expected_failure
) {
640 out
<< col_yellow
<< "XFAIL (" << expected_failure
643 out
<< col_red
<< "FAIL: ";
645 out
<< "UNKNOWN EXCEPTION" << col_reset
;
646 write_and_clear_tout();
647 return expected_failure
? XFAIL
: FAIL
;
650 if (expected_failure
) {
651 // Testcase marked as expected to fail but actually passed.
652 out
<< ' ' << col_red
<< "XPASS (" << expected_failure
<< ")"
654 write_and_clear_tout();
661 const char *signame
= "SIGNAL";
662 #if defined HAVE_SIGACTION && defined SA_SIGINFO
663 bool show_addr
= true;
665 bool show_addr
= false;
668 case SIGSEGV
: signame
= "SIGSEGV"; break;
669 case SIGFPE
: signame
= "SIGFPE"; break;
670 case SIGILL
: signame
= "SIGILL"; break;
672 case SIGBUS
: signame
= "SIGBUS"; break;
682 signame
= "SIGSTKFLT";
687 out
<< " " << col_red
<< signame
;
689 out
<< " at " << sigaddr
;
692 write_and_clear_tout();
698 test_driver::run_tests(vector
<string
>::const_iterator b
,
699 vector
<string
>::const_iterator e
)
701 return do_run_tests(b
, e
);
705 test_driver::run_tests()
707 const vector
<string
> blank
;
708 return do_run_tests(blank
.begin(), blank
.end());
712 test_driver::do_run_tests(vector
<string
>::const_iterator b
,
713 vector
<string
>::const_iterator e
)
716 bool check_name
= !m
.empty();
718 test_driver::result res
;
720 for (const test_desc
*test
= tests
; test
->name
; ++test
) {
721 bool do_this_test
= !check_name
;
722 if (!do_this_test
&& m
.find(test
->name
) != m
.end())
725 // if this test is "foo123" see if "foo" was listed
726 // this way "./testprog foo" can run foo1, foo2, etc.
727 string t
= test
->name
;
729 i
= t
.find_last_not_of("0123456789") + 1;
730 if (i
!= string::npos
) {
732 if (m
.find(t
) != m
.end()) do_this_test
= true;
736 out
<< "Running test: " << test
->name
<< "...";
738 auto starttime
= chrono::high_resolution_clock::now();
739 test_driver::test_result test_res
= runtest(test
);
740 auto endtime
= chrono::high_resolution_clock::now();
741 auto test_duration
= chrono::duration_cast
<chrono::duration
<double>>
742 (endtime
- starttime
);
745 backendmanager
->clean_up();
751 if (test_duration
.count() >= SLOW_TEST_THRESHOLD
) {
752 slow_tests
.emplace_back(test
->name
,
753 test_duration
.count());
756 if (verbose
|| !use_cr
) {
757 out
<< col_green
<< " ok" << col_reset
<< '\n';
770 if (abort_on_error
) {
771 throw "Test failed - aborting further tests";
777 if (abort_on_error
) {
778 throw "Test marked as XFAIL passed - aborting further tests";
784 // ignore the result of this test.
795 cout
<< "Usage: " << argv0
<< " [-v|--verbose] [-o|--abort-on-error] "
796 << opt_help
<< "[TESTNAME]...\n"
797 " " << argv0
<< " [-h|--help]\n";
801 /* Needs C linkage so we can pass it to atexit() without problems. */
803 // Call upon program exit if there's more than one test run.
807 test_driver::report(test_driver::total
, "total");
812 test_driver::report(const test_driver::result
&r
, const string
&desc
)
814 // Report totals at the end if we reported two or more subtotals.
815 if (++runs
== 2) atexit(report_totals
);
817 if (r
.succeeded
!= 0 || r
.failed
!= 0) {
818 cout
<< argv0
<< " " << desc
<< ": ";
820 if (r
.failed
== 0 && r
.xpassed
== 0)
823 cout
<< col_green
<< r
.succeeded
<< col_reset
<< " tests passed";
826 cout
<< ", " << col_red
<< r
.failed
<< col_reset
<< " failed";
829 cout
<< ", " << col_red
<< r
.xpassed
<< col_reset
830 << " expected failures passed";
833 cout
<< ", " << col_yellow
<< r
.xfailed
<< col_reset
834 << " expected failures";
837 cout
<< ", " << col_yellow
<< r
.skipped
<< col_reset
843 if (!slow_tests
.empty()) {
844 const char* sep
= "Slow tests: ";
845 for (auto& test
: slow_tests
) {
846 cout
<< sep
<< test
.first
<< " (" << test
.second
<< " s)";
856 test_driver::add_command_line_option(const string
&l
, char s
, string
* arg
)
858 short_opts
.insert(make_pair(int(s
), arg
));
867 test_driver::parse_command_line(int argc
, char **argv
)
872 if (RUNNING_ON_VALGRIND
) {
873 if (getenv("XAPIAN_TESTSUITE_VALGRIND") != NULL
) {
874 // Open the valgrind log file, and unlink it.
875 string fname
= ".valgrind.log." + str(getpid());
876 vg_log_fd
= open(fname
.c_str(), O_RDONLY
|O_NONBLOCK
|O_CLOEXEC
);
877 if (vg_log_fd
!= -1) unlink(fname
.c_str());
884 bool colourise
= true;
885 const char *p
= getenv("XAPIAN_TESTSUITE_OUTPUT");
886 if (p
== NULL
|| !*p
|| strcmp(p
, "auto") == 0) {
887 colourise
= isatty(1);
888 } else if (strcmp(p
, "plain") == 0) {
892 col_red
= "\x1b[1m\x1b[31m";
893 col_green
= "\x1b[1m\x1b[32m";
894 col_yellow
= "\x1b[1m\x1b[33m";
895 col_reset
= "\x1b[0m";
901 static const struct option long_opts
[] = {
902 {"verbose", no_argument
, 0, 'v'},
903 {"abort-on-error", no_argument
, 0, 'o'},
904 {"help", no_argument
, 0, 'h'},
908 string short_opts_string
= "voh";
909 map
<int, string
*>::const_iterator i
;
910 for (i
= short_opts
.begin(); i
!= short_opts
.end(); ++i
) {
911 short_opts_string
+= char(i
->first
);
912 short_opts_string
+= ':';
914 const char * opts
= short_opts_string
.c_str();
917 while ((c
= gnu_getopt_long(argc
, argv
, opts
, long_opts
, 0)) != -1) {
923 abort_on_error
= true;
926 i
= short_opts
.find(c
);
927 if (i
!= short_opts
.end()) {
928 i
->second
->assign(optarg
);
931 // -h or unrecognised option
933 return; // usage() doesn't return ...
939 const char *p
= getenv("VERBOSE");
942 if (!parse_unsigned(p
, temp
)) {
943 throw "VERBOSE must be a non-negative integer";
949 while (argv
[optind
]) {
950 test_names
.push_back(string(argv
[optind
]));
956 test_driver::run(const test_desc
*tests
)
958 test_driver
driver(tests
);
960 test_driver::result myresult
;
961 myresult
= driver
.run_tests(test_names
.begin(), test_names
.end());
963 subtotal
+= myresult
;
965 // Return value is a Unix-style exit code, so 0 for success and 1 for
967 return myresult
.failed
> 0 || myresult
.xpassed
> 0;
971 TEST_EQUAL_DOUBLE_(double a
, double b
)
973 if (a
== b
) return true;
974 return (ceil(log10(max(fabs(a
), fabs(b
)))) - log10(fabs(a
- b
)) > DBL_DIG
);