//////////////
+/*!
+ super helpful macro that shows the current stack trace and checks it for validity.
+ this shouldn't impact the trace, since all the code is embedded inline from the macro.
+ this does require that LOG() is defined, and that a failure return value is expected
+ from the embedding function.
+*/
+#define GET_AND_TEST_STACK_TRACE(header, failure_return) { \
+ int trace_size = program_wide_stack_trace().full_trace_size(); \
+ char *stack_trace = program_wide_stack_trace().full_trace(); \
+ ASSERT_TRUE(trace_size >= strlen(stack_trace) + 1, "insufficient estimated stack trace size"); \
+ if (trace_size < strlen(stack_trace) + 1) { \
+ /* error condition here; we are supposed to get the actual size we would need to allocate! */ \
+ LOG(a_sprintf("failure in stack trace return: estimated size (%d) was less than actual (%d)", \
+ trace_size, strlen(stack_trace))); \
+ /* mandatory free step for newly allocated string. */ \
+ free(stack_trace); \
+ return failure_return; \
+ } \
+ ASSERT_TRUE(strlen(stack_trace) > 1, "empty stack trace"); \
+ if (strlen(stack_trace) < 2) { \
+ LOG("failure in stack trace return: the trace output string was empty!"); \
+ return failure_return; \
+ } \
+ LOG(astring("\n\n################\n\n") + header + "\n" + stack_trace); \
+ /* mandatory free step for newly allocated string. */ \
+ free(stack_trace); \
+}
+
+//////////////
+
//! a small object that represents a stack trace in progress.
/*! the object will automatically be destroyed when the containing scope
exits. this enables a users of the stack tracker to simply label their
*/
inline void no_op() { /* do nothing. */ }
#define frame_tracking_instance
+ #define GET_AND_TEST_STACK_TRACE(header, failure_return) no_op();
#define __trail_of_function(p1, p2, p3, p4, p5) no_op();
- // the above actually trades on the name of the object we'd normally
- // define. it must match the object name in the FUNCDEF macro.
inline void update_current_stack_frame_line_number(int line) { /* more nothing. */ }
#endif // ENABLE_CALLSTACK_TRACKING
-/*****************************************************************************\
+/*
*
* Name : test_callstack_tracker
* Author : Chris Koeritz
*
* Puts the callstack tracking code through its paces, a bit.
*
-*******************************************************************************
-* Copyright (c) 1992-$now By Author. This program is free software; you can *
-* redistribute it and/or modify it under the terms of the GNU General Public *
-* License as published by the Free Software Foundation; either version 2 of *
-* the License or (at your option) any later version. This is online at: *
-* http://www.fsf.org/copyleft/gpl.html *
-* Please send any updates to: fred@gruntose.com *
-\*****************************************************************************/
+****
+* Copyright (c) 1992-$now By Author. This program is free software; you can
+* redistribute it and/or modify it under the terms of the GNU General Public
+* License as published by the Free Software Foundation; either version 2 of
+* the License or (at your option) any later version. This is online at:
+* http://www.fsf.org/copyleft/gpl.html
+* Please send any updates to: fred@gruntose.com
+*/
#include <basis/functions.h>
#include <basis/guards.h>
#include <structures/string_array.h>
#include <unit_test/unit_base.h>
-//#include <string.h>
-
using namespace application;
using namespace basis;
using namespace filesystem;
//////////////
-/*
- super helpful macro that shows the current stack trace and checks it for validity.
- this shouldn't impact the trace, since it's all embedded inline from the macro.
-*/
-#define SHOW_TRACE_AND_CHECK_IT(message) { \
- int trace_size = program_wide_stack_trace().full_trace_size(); \
- char *stack_trace = program_wide_stack_trace().full_trace(); \
- ASSERT_TRUE(trace_size >= strlen(stack_trace) + 1, "insufficient estimated stack trace size"); \
- if (trace_size < strlen(stack_trace) + 1) { \
- /* error condition here; we are supposed to get the actual size we would need to allocate! */ \
- LOG(a_sprintf("failure in stack trace return: estimated size (%d) was less than actual (%d)", \
- trace_size, strlen(stack_trace))); \
- /* mandatory free step for newly allocated string. */ \
- free(stack_trace); \
- return 1; \
- } \
- ASSERT_TRUE(strlen(stack_trace) > 1, "empty stack trace"); \
- if (strlen(stack_trace) < 2) { \
- LOG("failure in stack trace return: the trace output string was empty!"); \
- return 1; \
- } \
- LOG(astring("\n\n################\n\n") + message + "\n" + stack_trace); \
- /* mandatory free step for newly allocated string. */ \
- free(stack_trace); \
-}
-
-//////////////
-
class test_callstack_tracker : virtual public unit_base, virtual public application_shell
{
public:
FUNCDEF("run_filestack_simple")
#ifdef ENABLE_CALLSTACK_TRACKING
// just shows this method's own stack.
- SHOW_TRACE_AND_CHECK_IT("trace of simple stack:");
+ GET_AND_TEST_STACK_TRACE("trace of simple stack:", 1);
#endif
return 0;
}
FUNCDEF("sub_call_4");
#ifdef ENABLE_CALLSTACK_TRACKING
// just shows this method's own stack.
- SHOW_TRACE_AND_CHECK_IT("trace of middling stack:");
+ GET_AND_TEST_STACK_TRACE("trace of middling stack:", 1);
#endif
return 0;
}
* Please send any updates to: fred@gruntose.com *
\*****************************************************************************/
+#include <application/callstack_tracker.h>
#include <application/hoople_main.h>
#include <basis/astring.h>
#include <basis/guards.h>
using namespace structures;
using namespace unit_test;
+class test_mutex; // forward.
+
#define DEBUG_MUTEX
// uncomment for a verbose test run.
#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print)
// our macro for logging with a timestamp.
-// expects guardian mutex to already be locked once when coming in.
-void test_recursive_locking(chaos &_rando)
+//////////////
+
+#undef UNIT_BASE_THIS_OBJECT
+#define UNIT_BASE_THIS_OBJECT (*this)
+
+class test_mutex : virtual public unit_base, virtual public application_shell
{
- int test_attempts = _rando.inclusive(MIN_SAME_THREAD_LOCKING_TESTS,
- MAX_SAME_THREAD_LOCKING_TESTS);
- int locked = 0;
- for (int i = 0; i < test_attempts; i++) {
- bool lock = !!(_rando.inclusive(0, 1));
- if (lock) {
- guard().lock();
- locked++; // one more lock.
- } else {
- if (locked > 0) {
- // must be sure we are not already locally unlocked completely.
- guard().unlock();
- locked--;
- }
- }
- }
- for (int j = 0; j < locked; j++) {
- // drop any locks we had left during the test.
- guard().unlock();
- }
-}
+public:
+ chaos _rando; // our randomizer.
+
+ test_mutex() : application_shell() {}
+
+ DEFINE_CLASS_NAME("test_mutex");
+
+ int execute();
+
+ void test_recursive_locking(chaos &_rando);
+ // invoked by the threads to do a little testing of locks.
+};
+
+//////////////
//hmmm: how are these threads different so far? they seem to do exactly
// the same thing. maybe one should eat chars from the string.
{
public:
chaos _rando; // our randomizer.
- unit_base &c_testing; // provides for test recording.
+ test_mutex &c_testing; // provides for test recording.
- piranha(unit_base &testing) : ethread(0), c_testing(testing) {
+ piranha(test_mutex &testing) : ethread(0), c_testing(testing) {
FUNCDEF("constructor");
safe_add(concurrent_biters, 1);
ASSERT_TRUE(concurrent_biters >= 1, "the piranha is very noticeable");
ASSERT_TRUE(grab_lock <= 1, "grab lock should not already be active");
protected_string += char(_rando.inclusive('a', 'z'));
- test_recursive_locking(_rando);
+ c_testing.test_recursive_locking(_rando);
safe_add(grab_lock, -1);
}
+
+ #ifdef ENABLE_CALLSTACK_TRACKING
+ GET_AND_TEST_STACK_TRACE(class_name() + "::" + func + " callstack:", );
+ #endif
+
// dropped the lock. snooze a bit.
if (!should_stop())
time_control::sleep_ms(_rando.inclusive(THREAD_PAUSE_LOWEST, THREAD_PAUSE_HIGHEST));
};
+//////////////
+
class barracuda : public ethread
{
public:
chaos _rando; // our randomizer.
- unit_base &c_testing; // provides for test recording.
+ test_mutex &c_testing; // provides for test recording.
- barracuda(unit_base &testing) : ethread(0), c_testing(testing) {
+ barracuda(test_mutex &testing) : ethread(0), c_testing(testing) {
FUNCDEF("constructor");
safe_add(concurrent_biters, 1);
ASSERT_TRUE(concurrent_biters >= 1, "our presence should have been noticed");
safe_add(grab_lock, 1);
ASSERT_TRUE(grab_lock <= 1, "grab lock should not already be active");
- test_recursive_locking(_rando);
+ c_testing.test_recursive_locking(_rando);
protected_string += char(_rando.inclusive('a', 'z'));
safe_add(grab_lock, -1);
guard().unlock();
+
+ #ifdef ENABLE_CALLSTACK_TRACKING
+ GET_AND_TEST_STACK_TRACE(class_name() + "::" + func + " callstack:", );
+ #endif
+
// done with the lock. sleep for a while.
if (!should_stop())
time_control::sleep_ms(_rando.inclusive(THREAD_PAUSE_LOWEST, THREAD_PAUSE_HIGHEST));
#undef UNIT_BASE_THIS_OBJECT
#define UNIT_BASE_THIS_OBJECT (*this)
-class test_mutex : virtual public unit_base, virtual public application_shell
-{
-public:
- chaos _rando; // our randomizer.
-
- test_mutex() : application_shell() {}
-
- DEFINE_CLASS_NAME("test_mutex");
-
- int execute();
-};
-
int test_mutex::execute()
{
FUNCDEF("execute");
#ifdef DEBUG_MUTEX
LOG("about to exit scope and dump automatic objects.");
#endif
+
+ #ifdef ENABLE_CALLSTACK_TRACKING
+ GET_AND_TEST_STACK_TRACE(class_name() + "::" + func + " mutex lock timing callstack:", 1);
+ #endif
}
#ifdef DEBUG_MUTEX
LOG(a_sprintf("indy %i: adding new piranha now.", i));
#endif
}
+
+ #ifdef ENABLE_CALLSTACK_TRACKING
+ GET_AND_TEST_STACK_TRACE(class_name() + "::" + func + a_sprintf(" starting fish indy #%d", i+1) + " callstack:", 1);
+ #endif
+
thread_list.append(t);
ethread *q = thread_list[thread_list.elements() - 1];
ASSERT_EQUAL(q, t, "amorph pointer equivalence is required");
#ifdef DEBUG_MUTEX
LOG("done exiting from all threads.");
+ #ifdef ENABLE_CALLSTACK_TRACKING
+ GET_AND_TEST_STACK_TRACE(class_name() + "::" + func + " after all threads have exited callstack:", 1);
+ #endif
+
LOG(astring(astring::SPRINTF, "the accumulated string had %d characters "
"which means\nthere were %d thread activations from %d threads.",
protected_string.length(), protected_string.length(),
return final_report();
}
+//////////////
+
+// expects guardian mutex to already be locked once when coming in.
+void test_mutex::test_recursive_locking(chaos &_rando)
+{
+ FUNCDEF("test_recursive_locking");
+ int test_attempts = _rando.inclusive(MIN_SAME_THREAD_LOCKING_TESTS,
+ MAX_SAME_THREAD_LOCKING_TESTS);
+ int locked = 0;
+ #ifdef ENABLE_CALLSTACK_TRACKING
+ GET_AND_TEST_STACK_TRACE(class_name() + "::" + func + " callstack:", );
+ #endif
+ for (int i = 0; i < test_attempts; i++) {
+ bool lock = !!(_rando.inclusive(0, 1));
+ if (lock) {
+ guard().lock();
+ locked++; // one more lock.
+ } else {
+ if (locked > 0) {
+ // must be sure we are not already locally unlocked completely.
+ guard().unlock();
+ locked--;
+ }
+ }
+ }
+ for (int j = 0; j < locked; j++) {
+ // drop any locks we had left during the test.
+ guard().unlock();
+ }
+}
+
+//////////////
+
HOOPLE_MAIN(test_mutex, )