[beast: 4/15] SFI: testing: add Test utilities for unit tests
- From: Tim Janik <timj src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [beast: 4/15] SFI: testing: add Test utilities for unit tests
- Date: Sun, 23 Jul 2017 18:58:09 +0000 (UTC)
commit c950298b6ee64c206e78b4e99d5625153d67dc7f
Author: Tim Janik <timj gnu org>
Date: Sun Jul 23 00:59:20 2017 +0200
SFI: testing: add Test utilities for unit tests
Signed-off-by: Tim Janik <timj gnu org>
sfi/Makefile.am | 4 +-
sfi/testing.cc | 233 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
sfi/testing.hh | 128 ++++++++++++++++++++++++++++++
3 files changed, 364 insertions(+), 1 deletions(-)
---
diff --git a/sfi/Makefile.am b/sfi/Makefile.am
index 686ef65..ed8494b 100644
--- a/sfi/Makefile.am
+++ b/sfi/Makefile.am
@@ -15,8 +15,9 @@ sfi_public_headers = $(strip \
sfitime.hh sfitypes.hh sfivalues.hh \
sfivisitors.hh sfiustore.hh \
sficxx.hh sfiring.hh sfimemory.hh sficomport.hh \
- sfitests.hh sfi.hh \
+ sfi.hh \
gbsearcharray.hh \
+ testing.hh \
)
sfi_all_sources = $(strip \
bcore.cc formatter.cc path.cc platform.cc strings.cc \
@@ -27,6 +28,7 @@ sfi_all_sources = $(strip \
sfitime.cc sfitypes.cc sfivalues.cc \
sfivisitors.cc sfiustore.cc \
sfiring.cc sfimemory.cc sficomport.cc \
+ testing.cc \
)
sfi_extra_sources = $(strip \
)
diff --git a/sfi/testing.cc b/sfi/testing.cc
new file mode 100644
index 0000000..16c9431
--- /dev/null
+++ b/sfi/testing.cc
@@ -0,0 +1,233 @@
+// This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
+#include "testing.hh"
+#include <algorithm>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#define TDEBUG(...) Bse::debug ("Test", __VA_ARGS__)
+
+namespace Bse {
+
+/** The Test namespace offers utilities for unit tests.
+ * The Test namespace is made available by <code> \#include <sfi/testing.hh> </code> <br/>
+ * See also sfi/testing.hh.
+ */
+namespace Test {
+
+static void
+test_assertion_failed()
+{
+ void *__p_[BSE_BACKTRACE_MAXDEPTH] = { 0, };
+ String btmsg = pretty_backtrace (__p_, backtrace_pointers (__p_, sizeof (__p_) / sizeof (__p_[0])),
__FILE__, __LINE__, NULL);
+ if (btmsg.size())
+ printerr ("%s", btmsg.c_str());
+ printerr ("Rapicorn::test_assertion_failed(): aborting...");
+ Bse::breakpoint();
+ abort();
+}
+
+/** Initialize the Bse core for a test program.
+ * Initializes Bse to execute unit tests by calling parse_settings_and_args()
+ * with args "autonomous=1" and "testing=1" and setting the application name.
+ * See also #$BSE_DEBUG.
+ */
+void
+init (int *argcp, char **argv, const StringVector &args)
+{
+ Aida::assertion_failed_hook (test_assertion_failed);
+ unsigned int flags = g_log_set_always_fatal (GLogLevelFlags (G_LOG_FATAL_MASK));
+ g_log_set_always_fatal (GLogLevelFlags (flags | G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL));
+ sfi_init (argcp, argv, Bse::cstrings_to_vector (":autonomous:testing:fatal-warnings:", NULL));
+}
+
+Timer::Timer (double deadline_in_secs) :
+ deadline_ (deadline_in_secs), test_duration_ (0), n_reps_ (0)
+{}
+
+Timer::~Timer ()
+{}
+
+double
+Timer::bench_time ()
+{
+ /* timestamp_benchmark() counts nano seconds since program start, so
+ * it's not going to exceed the 52bit double mantissa too fast.
+ */
+ return timestamp_benchmark() / 1000000000.0;
+}
+
+#define DEBUG_LOOPS_NEEDED(...) while (0) printerr (__VA_ARGS__)
+
+int64
+Timer::loops_needed ()
+{
+ if (samples_.size() < 3)
+ {
+ n_reps_ = MAX (1, n_reps_);
+ DEBUG_LOOPS_NEEDED ("loops_needed: %d\n", n_reps_);
+ return n_reps_; // force significant number of test runs
+ }
+ double resolution = timestamp_resolution() / 1000000000.0;
+ const double deadline = MAX (deadline_ == 0.0 ? 0.005 : deadline_, resolution * 10000.0);
+ if (test_duration_ < deadline * 0.2)
+ {
+ // increase the number of tests per run to gain more accuracy
+ n_reps_ = MAX (n_reps_ + 1, int64 (n_reps_ * 1.5)) | 1;
+ DEBUG_LOOPS_NEEDED ("loops_needed: %d\n", n_reps_);
+ return n_reps_;
+ }
+ if (test_duration_ < deadline)
+ {
+ DEBUG_LOOPS_NEEDED ("loops_needed: %d\n", n_reps_);
+ return n_reps_;
+ }
+ DEBUG_LOOPS_NEEDED ("loops_needed: %d\n", 0);
+ return 0;
+}
+
+void
+Timer::submit (double elapsed, int64 repetitions)
+{
+ test_duration_ += elapsed;
+ double resolution = timestamp_resolution() / 1000000000.0;
+ if (elapsed >= resolution * 500.0) // force error below 5%
+ samples_.push_back (elapsed / repetitions);
+ else
+ n_reps_ = (n_reps_ + n_reps_) | 1; // double n_reps_ to yield significant times
+}
+
+void
+Timer::reset()
+{
+ samples_.resize (0);
+ test_duration_ = 0;
+ n_reps_ = 0;
+}
+
+double
+Timer::min_elapsed () const
+{
+ double m = DBL_MAX;
+ for (size_t i = 0; i < samples_.size(); i++)
+ m = MIN (m, samples_[i]);
+ return m;
+}
+
+double
+Timer::max_elapsed () const
+{
+ double m = 0;
+ for (size_t i = 0; i < samples_.size(); i++)
+ m = MAX (m, samples_[i]);
+ return m;
+}
+
+static String
+ensure_newline (const String &s)
+{
+ if (!s.size() || s[s.size()-1] != '\n')
+ return s + "\n";
+ return s;
+}
+
+static __thread String *thread_test_start = NULL;
+
+void
+test_output (int kind, const String &msg)
+{
+ if (!thread_test_start)
+ thread_test_start = new String();
+ String &test_start = *thread_test_start;
+ String prefix, sout;
+ bool aborting = false;
+ switch (kind)
+ {
+ case 'S': // TSTART()
+ if (!test_start.empty())
+ return test_output ('F', string_format ("Unfinished Test: %s\n", test_start));
+ test_start = msg;
+ sout = " START… " + ensure_newline (msg);
+ break;
+ case 'D': // TDONE() - test passed
+ if (test_start.empty())
+ return test_output ('F', "Extraneous TDONE() call");
+ test_start = "";
+ sout = " …DONE " + ensure_newline (msg);
+ break;
+ case 'I': // TNOTE() - verbose test message
+ if (verbose())
+ sout = " NOTE " + ensure_newline (msg);
+ break;
+ case 'P':
+ sout = " PASS " + ensure_newline (msg);
+ break;
+ case 'F':
+ sout = " FAIL " + ensure_newline (msg);
+ aborting = true;
+ break;
+ default:
+ sout = " INFO " + ensure_newline (msg);
+ break;
+ }
+ if (!sout.empty()) // test message output
+ {
+ fflush (stderr);
+ fputs (sout.c_str(), stdout);
+ fflush (stdout);
+ }
+ if (aborting)
+ {
+ breakpoint();
+ }
+}
+
+bool
+slow()
+{
+ static bool cached_slow = feature_toggle_bool (getenv ("BSE_TEST"), "slow");
+ return cached_slow;
+}
+
+bool
+verbose()
+{
+ static bool cached_verbose = feature_toggle_bool (getenv ("BSE_TEST"), "verbose");
+ return cached_verbose;
+}
+
+int
+run (void)
+{
+ // FIXME: run_tests();
+ return 0;
+}
+
+uint64_t
+random_int64 ()
+{
+ return Bse::random_int64();
+}
+
+int64_t
+random_irange (int64_t begin, int64_t end)
+{
+ return Bse::random_irange (begin, end);
+}
+
+double
+random_float ()
+{
+ return Bse::random_float();
+}
+
+double
+random_frange (double begin, double end)
+{
+ return Bse::random_frange (begin, end);
+}
+
+} } // Bse::Test
diff --git a/sfi/testing.hh b/sfi/testing.hh
new file mode 100644
index 0000000..56e093e
--- /dev/null
+++ b/sfi/testing.hh
@@ -0,0 +1,128 @@
+// This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
+#ifndef __BSE_TESTING_HH__
+#define __BSE_TESTING_HH__
+
+#include <sfi/sfi.hh>
+
+namespace Bse {
+namespace Test {
+
+void init (int *argcp, char **argv, const StringVector &args = StringVector());
+
+
+// Test Macros
+#define TSTART(...) Bse::Test::test_output ('S', ::Bse::string_format (__VA_ARGS__)) ///< Print
message once a test case starts.
+#define TDONE() Bse::Test::test_output ('D', "") ///< Print
message for test case end.
+#define TPASS(...) Bse::Test::test_output ('P', ::Bse::string_format (__VA_ARGS__)) ///< Print
a message about a passing test.
+#define TNOTE(...) Bse::Test::test_output ('I', ::Bse::string_format (__VA_ARGS__)) ///< Print
a message from a test program.
+#define TCHECK(cond, ...) Bse::Test::test_output (bool (cond) ? 'P' : 'F', \
+ ::Bse::string_format (__VA_ARGS__)) ///<
Verbose assertion, calls TPASS() on success.
+#define TCMPS(a,cmp,b) TCMP_op (a,cmp,b,#a,#b,Bse::Test::_as_strptr) ///<
Variant of TCMP() for C strings.
+#define TCMP(a,cmp,b) TCMP_op (a,cmp,b,#a,#b,) ///< Compare @a a and @a b according to
operator @a cmp, verbose on failiure.
+#define TASSERT(cond) TASSERT__AT (__LINE__, cond) ///< Unconditional test assertion, enters
breakpoint if not fullfilled.
+#define TASSERT_AT(LINE, cond) TASSERT__AT (LINE, cond) ///< Unconditional test assertion for deputy
__LINE__.
+#define TOK() do {} while (0) ///< Deprecated progress indicator, tests
generally need to run fast.
+
+/** Class for profiling benchmark tests.
+ * UseCase: Benchmarking function implementations, e.g. to compare sorting implementations.
+ */
+class Timer {
+ const double deadline_;
+ vector<double> samples_;
+ double test_duration_;
+ int64 n_reps_;
+ int64 loops_needed ();
+ void reset ();
+ void submit (double elapsed, int64 repetitions);
+ static double bench_time ();
+public:
+ /// Create a Timer() instance, specifying an optional upper bound for test durations.
+ explicit Timer (double deadline_in_secs = 0);
+ virtual ~Timer ();
+ int64 n_reps () const { return n_reps_; } ///< Number of benchmark repetitions
to execute
+ double test_elapsed () const { return test_duration_; } ///< Seconds spent in benchmark()
+ double min_elapsed () const; ///< Minimum time benchmarked for a @a callee() call.
+ double max_elapsed () const; ///< Maximum time benchmarked for a @a callee() call.
+ /**
+ * @param callee A callable function or object.
+ * Method to benchmark the execution time of @a callee.
+ * @returns Minimum runtime in seconds,
+ */
+ template<typename Callee>
+ double benchmark (Callee callee);
+};
+
+
+// === test maintenance ===
+int run (); ///< Run all registered tests.
+bool verbose (); ///< Indicates whether tests should run verbosely.
+bool slow (); ///< Indicates whether slow tests should be run.
+void test_output (int kind, const String &string);
+
+
+/// == Stringify Args ==
+inline String stringify_arg (const char *a, const char *str_a) { return a ?
string_to_cquote (a) : "(__null)"; }
+template<class V> inline String stringify_arg (const V *a, const char *str_a) { return string_format
("%p", a); }
+template<class A> inline String stringify_arg (const A &a, const char *str_a) { return str_a; }
+template<> inline String stringify_arg<float> (const float &a, const char *str_a) { return string_format
("%.8g", a); }
+template<> inline String stringify_arg<double> (const double &a, const char *str_a) { return string_format
("%.17g", a); }
+template<> inline String stringify_arg<bool> (const bool &a, const char *str_a) { return string_format
("%u", a); }
+template<> inline String stringify_arg<int8> (const int8 &a, const char *str_a) { return string_format
("%d", a); }
+template<> inline String stringify_arg<int16> (const int16 &a, const char *str_a) { return string_format
("%d", a); }
+template<> inline String stringify_arg<int32> (const int32 &a, const char *str_a) { return string_format
("%d", a); }
+template<> inline String stringify_arg<int64> (const int64 &a, const char *str_a) { return string_format
("%lld", a); }
+template<> inline String stringify_arg<uint8> (const uint8 &a, const char *str_a) { return string_format
("0x%02x", a); }
+template<> inline String stringify_arg<uint16> (const uint16 &a, const char *str_a) { return string_format
("0x%04x", a); }
+template<> inline String stringify_arg<uint32> (const uint32 &a, const char *str_a) { return string_format
("0x%08x", a); }
+template<> inline String stringify_arg<uint64> (const uint64 &a, const char *str_a) { return string_format
("0x%08Lx", a); }
+template<> inline String stringify_arg<String> (const String &a, const char *str_a) { return
string_to_cquote (a); }
+inline const char* _as_strptr (const char *s) { return s; } // implementation detail
+
+// == Deterministic random numbers for tests ===
+uint64_t random_int64 (); ///< Return random int for
reproduceble tests.
+int64_t random_irange (int64_t begin, int64_t end); ///< Return random int within range
for reproduceble tests.
+double random_float (); ///< Return random double for
reproduceble tests.
+double random_frange (double begin, double end); ///< Return random double within
range for reproduceble tests.
+
+
+// == Implementations ==
+/// @cond
+
+#define TASSERT__AT(LINE, cond) \
+ do { if (BSE_ISLIKELY (cond)) break; \
+ ::Bse::assertion_failed (__FILE__, LINE, #cond); } while (0)
+
+#define TCMP_op(a,cmp,b,sa,sb,cast) \
+ do { if (a cmp b) break; \
+ Bse::String __tcmp_va = Bse::Test::stringify_arg (cast (a), #a); \
+ Bse::String __tcmp_vb = Bse::Test::stringify_arg (cast (b), #b), \
+ __tcmp_as = Bse::string_format ("'%s %s %s': %s %s %s", \
+ sa, #cmp, sb, \
+ __tcmp_va.c_str(), \
+ #cmp, \
+ __tcmp_vb.c_str()); \
+ ::Bse::assertion_failed (__FILE__, __LINE__, __tcmp_as.c_str()); \
+ } while (0)
+
+template<typename Callee> double
+Timer::benchmark (Callee callee)
+{
+ reset();
+ for (int64 runs = loops_needed(); runs; runs = loops_needed())
+ {
+ int64 n = runs;
+ const double start = bench_time();
+ while (BSE_ISLIKELY (n--))
+ callee();
+ const double stop = bench_time();
+ submit (stop - start, runs);
+ }
+ return min_elapsed();
+}
+
+/// @endcond
+
+} // Test
+} // Bse
+
+#endif /* __BSE_TESTING_HH__ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]