## hostthread-unit-test.pkg
#
# Unit/regression test functionality for
#
#
src/lib/std/src/hostthread.pkg#
# (Multiple posix threads sharing the Mythryl
# heap and executing Mythryl code.)
# Compiled by:
#
src/lib/test/unit-tests.lib# Run by:
#
src/lib/test/all-unit-tests.pkg###############################################################################
# To-do / status:
#
# o XXX SUCKO FIXME: verify_that_basic_condition_variable_stuff_works () hangs if we delete the hostthread::hostthread_exit ();
# o XXX SUCKO FIXME: verify_that_basic_condition_variable_stuff_works () hangs any subthread throws an exception instead of calling hostthread:hostthread_exit ();
###############################################################################
stipulate
package fil = file__premicrothread; # file__premicrothread is from
src/lib/std/src/posix/file--premicrothread.pkg package hth = hostthread; # hostthread is from
src/lib/std/src/hostthread.pkg package mtx = winix_file_io_mutex; # winix_file_io_mutex is from
src/lib/std/src/io/winix-file-io-mutex.pkg package u1w = one_word_unt; # one_word_unt is from
src/lib/std/one-word-unt.pkgherein
package hostthread_unit_test {
#
include package unit_test; # unit_test is from
src/lib/src/unit-test.pkg fun pline line_fn # Define a hostthread-safe function to output lines.
= # "pline" is mnemonic for for "print_line" but also "parallel_print_line" and "hostthread_safe_print_line" and such. :-)
hth::with_mutex_do mtx::mutex {.
#
line = line_fn () + "\n";
#
fil::write (fil::stdout, line );
};
package redblack_tree_torture_test {
#
include package int_red_black_map; # int_red_black_map is from
src/lib/src/int-red-black-map.pkg # When debugging uncomment the following lines and
# add more log_if calls as appropriate:
#
# fil::set_logger_to (log::log_TO_FILE "xyzzy.log");
# log_if = log::log_if fil::compiler_logging 0;
# log_if {. "Top of script"; };
# These values don't constitute
# a torture test, they are just
# intended as insurance against
# bitrot:
#
loops = 3; # For a real torture test, 10 to 100 is a good range.
limit = 100; # For a real torture test, >10,000 is a good size range.
fun subhostthread_fn id ()
=
{
hth::set_hostthread_name (sprintf "subhostthread %d" id);
for (loop = 0; loop < loops; ++loop) {
#
# pline {. sprintf "loop %d thread %d" loop id; }; # Print narration line with proper mutual-exclusion vs other hostthreads.
# Commented out at present to make 'make check' output look neater.
# Create a map by successive appends:
#
my test_map
=
for (m = empty, i = 0; i < limit; ++i; m) {
m = set (m, i, i);
assert' (all_invariants_hold m ); # We use assert'() here instead of assert() because printing the '.'s involves microthread ops which we're not allowed to do in a secondary hostthread.
assert' (not (is_empty m) );
assert' (the (first_val_else_null m) == 0 );
assert' ( vals_count m == i+1 );
assert' (#1 (the (first_keyval_else_null m)) == 0 );
assert' (#2 (the (first_keyval_else_null m)) == 0 );
};
# Check resulting map's contents:
#
for (i = 0; i < limit; ++i) {
#
assert' ((the (get (test_map, i))) == i );
};
# Try removing at all possible positions in map:
#
for (map' = test_map, i = 0; i < limit; ++i) {
#
map'' = drop (map', i);
assert' (all_invariants_hold map'' );
};
assert' (is_empty empty );
};
hostthread::hostthread_exit ();
};
fun run ()
=
{
# heap_debug::breakpoint_1 ();
# Should write logic to automatically adapt
# to number of cores on host machine: # XXX SUCKO FIXME
subhostthread0 = hostthread::spawn_hostthread (subhostthread_fn 0);
subhostthread1 = hostthread::spawn_hostthread (subhostthread_fn 1);
# subhostthread2 = hostthread::spawn_hostthread (subhostthread_fn 2);
# subhostthread3 = hostthread::spawn_hostthread (subhostthread_fn 3);
# subhostthread4 = hostthread::spawn_hostthread (subhostthread_fn 4);
# subhostthread5 = hostthread::spawn_hostthread (subhostthread_fn 5);
hostthread::join_hostthread subhostthread0;
hostthread::join_hostthread subhostthread1;
# hostthread::join_hostthread subhostthread2;
# hostthread::join_hostthread subhostthread3;
# hostthread::join_hostthread subhostthread4;
# hostthread::join_hostthread subhostthread5;
};
};
#
name = "src/lib/std/src/hostthread-unit-test.pkg";
log_if = fil::log_if fil::compiler_logging 0;
fun verify_that_basic_spawn_and_join_are_working ()
=
{ foo = REF 0;
#
fun subthread_fn ()
=
{
hth::set_hostthread_name "subthread A";
makelib::scripting_globals::sleep 0.01; # Give main thread a good chance to finish early if join_hostthread is totally broken.
#
foo := 1; # Give main thread visible evidence that we've run.
#
hostthread::hostthread_exit (); # Die.
};
subthread = hostthread::spawn_hostthread subthread_fn; # Spawn a subthread.
#
hostthread::join_hostthread subthread; # Wait for subthread to exit.
#
assert (*foo == 1); # Verify that subthread did what we expected.
};
fun verify_that_basic_mutex_stuff_is_working ()
=
{ foo = REF 0;
#
mutex = hostthread::make_mutex ();
hostthread::acquire_mutex mutex;
fun subthread_fn ()
=
{
hth::set_hostthread_name "subthread B";
hostthread::acquire_mutex mutex;
#
foo := 1;
#
hostthread::release_mutex mutex;
#
hostthread::hostthread_exit ();
};
hostthread = hostthread::spawn_hostthread subthread_fn;
makelib::scripting_globals::sleep 0.01; # Give child a chance to run if mutex does not block properly.
assert (*foo == 0); # Verify that child has not run.
hostthread::release_mutex mutex; # Unblock child.
hostthread::join_hostthread hostthread; # Join child.
hostthread::free_mutex mutex;
assert (*foo == 1); # Verify that child has now run.
};
fun verify_that_successful_trylock_works ()
=
{
mutex = hostthread::make_mutex ();
#
fun subthread_fn ()
=
{
hth::set_hostthread_name "subthread C";
try_result = hostthread::try_mutex mutex;
assert' (try_result == hostthread::ACQUIRED_MUTEX);
#
hostthread::release_mutex mutex; # Why not.
hostthread::hostthread_exit ();
};
hostthread = hostthread::spawn_hostthread subthread_fn;
hostthread::join_hostthread hostthread;
hostthread::free_mutex mutex;
};
fun verify_that_unsuccessful_trylock_works ()
=
{ mutex = hostthread::make_mutex ();
#
hostthread::acquire_mutex mutex;
fun subthread_fn ()
=
{
hth::set_hostthread_name "subthread D";
assert' ((hostthread::try_mutex mutex) == hostthread::MUTEX_WAS_UNAVAILABLE);
#
hostthread::hostthread_exit ();
};
hostthread = hostthread::spawn_hostthread subthread_fn;
makelib::scripting_globals::sleep 1.11;
hostthread::release_mutex mutex;
hostthread::join_hostthread hostthread;
hostthread::free_mutex mutex;
};
fun verify_that_basic_barrier_wait_works ()
=
{ foo = REF 0;
#
mutex = hostthread::make_mutex ();
barrier = hostthread::make_barrier ();
hostthread::set_barrier { barrier, threads => 3 };
fun subthread_fn ()
=
{
hth::set_hostthread_name "subthread E";
hostthread::wait_on_barrier barrier;
#
hostthread::acquire_mutex mutex;
#
foo := *foo + 1;
#
hostthread::release_mutex mutex;
#
hostthread::hostthread_exit ();
};
subthread1 = hostthread::spawn_hostthread subthread_fn;
subthread2 = hostthread::spawn_hostthread subthread_fn;
makelib::scripting_globals::sleep 0.01;
assert (*foo == 0);
hostthread::wait_on_barrier barrier;
hostthread::join_hostthread subthread1;
hostthread::join_hostthread subthread2;
assert (*foo == 2);
hostthread::free_mutex mutex;
hostthread::free_barrier barrier;
};
fun verify_barrier_wait_return_value ()
=
{
foo = REF 0; # Every hostthread will increment this.
bar = REF 0; # Only the One True Hostthread will increment this.
mutex = hostthread::make_mutex ();
barrier = hostthread::make_barrier ();
hostthread::set_barrier { barrier, threads => 3 };
fun subthread_fn ()
=
{
hth::set_hostthread_name "subthread F";
if (hostthread::wait_on_barrier barrier) # Exactly one hostthread waiting on barrier should see a TRUE return value.
#
hostthread::acquire_mutex mutex;
#
foo := *foo + 1;
bar := *bar + 1;
#
hostthread::release_mutex mutex;
else
hostthread::acquire_mutex mutex;
#
foo := *foo + 1;
#
hostthread::release_mutex mutex;
fi;
#
hostthread::hostthread_exit ();
};
subthread1 = hostthread::spawn_hostthread subthread_fn;
subthread2 = hostthread::spawn_hostthread subthread_fn;
subthread3 = hostthread::spawn_hostthread subthread_fn;
hostthread::join_hostthread subthread1;
hostthread::join_hostthread subthread2;
hostthread::join_hostthread subthread3;
assert (*foo == 3);
assert (*bar == 1);
hostthread::free_mutex mutex;
hostthread::free_barrier barrier;
};
fun verify_that_basic_condition_variable_stuff_works ()
=
{ # When debugging uncomment the following lines and
# add more log_if calls as appropriate:
#
# fil::set_logger_to (log::log_TO_FILE "xyzzy.log");
# log_if = log::log_if fil::compiler_logging 0;
# log_if {. "verify_that_basic_condition_variable_stuff_works"; };
loops = 10;
foo = REF 0;
last = REF 0;
mutex = hostthread::make_mutex ();
condvar = hostthread::make_condvar ();
fun subthread_fn id ()
=
{
hth::set_hostthread_name (sprintf "subthread %d" id);
hostthread::acquire_mutex mutex;
#
for (i = 0; i < loops; ++i) {
# printf "subthread_fn(%d) loop d=%d\n" id i; fil::flush fil::stdout; log_if {. sprintf "subthread_fn(%d)/CCC i d=%d" id i; };
for (((*last + 1) % 4) != id) {
#
hostthread::wait_on_condvar (condvar, mutex);
};
foo := *foo + 1;
last := id;
hostthread::broadcast_condvar condvar;
};
#
hostthread::release_mutex mutex;
hostthread::hostthread_exit ();
};
subthread0 = hostthread::spawn_hostthread (subthread_fn 0);
subthread1 = hostthread::spawn_hostthread (subthread_fn 1);
subthread2 = hostthread::spawn_hostthread (subthread_fn 2);
subthread3 = hostthread::spawn_hostthread (subthread_fn 3);
hostthread::join_hostthread subthread0;
hostthread::join_hostthread subthread1;
hostthread::join_hostthread subthread2;
hostthread::join_hostthread subthread3;
hostthread::free_mutex mutex;
hostthread::free_condvar condvar;
assert TRUE; # printf "Script z DONE\n"; fil::flush fil::stdout; log_if {. "Script z DONE."; };
}; # fun verify_that_basic_condition_variable_stuff_works
fun run ()
=
{ printf "\nDoing %s:\n" name;
#
assert( hostthread::get_hostthread_ptid() != u1w::from_int 0 or
hostthread::get_hostthread_ptid() == u1w::from_int 0
);
#
# This is mostly just to verify that src/c/lib/hostthread/libmythryl-hostthread.c
# and src/c/hostthread/hostthread-on-posix-threads.c
# were compiled and linked into our
# runtime executable.
verify_that_basic_spawn_and_join_are_working ();
verify_that_basic_mutex_stuff_is_working ();
verify_that_successful_trylock_works ();
verify_that_unsuccessful_trylock_works ();
verify_that_basic_barrier_wait_works ();
verify_barrier_wait_return_value ();
verify_that_basic_condition_variable_stuff_works ();
#
redblack_tree_torture_test::run ();
#
summarize_unit_tests name;
};
};
end;
##########################################################################
# Script version of above \\ verify_that_basic_condition_variable_stuff_works.c
# This is useful when debugging:
#
# #!/usr/bin/mythryl
#
# {
# # When debugging uncomment the following lines and
# # add more log_if calls as appropriate:
# #
# # fil::set_logger_to (log::log_TO_FILE "xyzzy.log");
# # log_if = log::log_if fil::compiler_logging 0;
# # log_if {. "Top of script z"; };
#
# loops = 10;
#
# foo = REF 0;
# last = REF 0;
#
# mutex = hostthread::make_mutex ();
# condvar = hostthread::make_condvar ();
#
# hostthread::set_up_mutex (mutex, NULL);
# hostthread::set_up_condvar (condvar, NULL);
#
# fun subthread_fn id ()
# =
# {
# hostthread::acquire_mutex mutex;
# #
# for (i = 0; i < loops; ++i) {
# # printf "subthread_fn(%d) loop d=%d\n" id i; fil::flush fil::stdout; log_if {. sprintf "subthread_fn(%d)/CCC i d=%d" id i; };
# for (((*last + 1) % 4) != id) {
#
# hostthread::wait_on_condvar (condvar, mutex);
# };
#
# foo := *foo + 1;
# last := id;
#
# hostthread::broadcast_condvar condvar;
# };
# #
# hostthread::release_mutex mutex;
#
# hostthread::hostthread_exit ();
# };
#
# subthread0 = hostthread::spawn_hostthread (subthread_fn 0);
# subthread1 = hostthread::spawn_hostthread (subthread_fn 1);
# subthread2 = hostthread::spawn_hostthread (subthread_fn 2);
# subthread3 = hostthread::spawn_hostthread (subthread_fn 3);
#
# hostthread::join_hostthread subthread0;
# hostthread::join_hostthread subthread1;
# hostthread::join_hostthread subthread2;
# hostthread::join_hostthread subthread3;
#
# hostthread::free_mutex mutex;
# hostthread::free_condvar condvar;
#
# # printf "Script z DONE\n"; fil::flush fil::stdout; log_if {. "Script z DONE."; };
# };
##########################################################################
# verify-that-basic-condition-variable-stuff-works.c
#
# This is a pure-C version of the above fun verify_that_basic_condition_variable_stuff_works ()
# I wrote it during debugging to verify that the problem was in my
# Mythryl implementation, not the underlying <pthread.h> code; I
# leave it here in case such verification is needed again at some point. -- 2011-12-13 CrT
#
# // Compile via:
# //
# // gcc -O2 -std=c99 -Wall -m32 -D_REENTRANT -pthread -lpthread -o verify-that-basic-condition-variable-stuff-works verify-that-basic-condition-variable-stuff-works.c
#
# #include <stdio.h>
# #include <stdlib.h>
#
# #include <stdarg.h>
# #include <string.h>
#
# #include <unistd.h>
# #include <errno.h>
#
# #include <sys/time.h>
# #include <sys/types.h>
#
# #include <pthread.h>
#
# #define MAX_BUF 4096
#
# pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
# pthread_cond_t condvar = PTHREAD_COND_INITIALIZER;
#
# int get_time_of_day (int* microseconds) {
# //===============
# //
# // Get time as (seconds,microseconds).
# //
#
# int c_sec;
# int c_usec;
#
# { struct timeval t;
# //
# gettimeofday (&t, NULL);
# c_sec = t.tv_sec;
# c_usec = t.tv_usec;
# }
#
# *microseconds = c_usec;
#
# return c_sec;
# }
#
# void log_if (const char * fmt, ...) {
#
# //
# int len;
# int seconds;
# int microseconds;
#
# char buf[ MAX_BUF ];
#
# va_list va;
#
# // Start by writing the timestamp into buf[].
# //
# // We match the timestamp formats in make_logstring in
# //
# //
src/lib/src/lib/thread-kit/src/lib/logger.pkg# // and
src/lib/std/src/io/winix-text-file-for-os-g--premicrothread.pkg# //
# // Making the gettimeofday() system call here
# // is a little bit risky in that the system
# // call might change the behavior being debugged,
# // but I think the tracelog timestamps are valuable
# // enough to justify the risk:
# //
# seconds = get_time_of_day( µseconds );
#
# // The intent here is
# //
# // 1) That doing unix 'sort' on a logfile will do the right thing:
# // sort first by time, then by process id, then by thread id.
# //
# // 2) To facilitate egrep/perl processing, e.g. doing stuff like
# // egrep 'pid=021456' logfile
# //
# // We fill in dummy tid= and (thread) name= values here to reduce
# // the need for special-case code when processing logfiles:
# //
# sprintf(buf,"time=%10d.%06d pid=%08d ptid=%08lx tid=00000000 name=%-16s msg=", seconds, microseconds, getpid(), (unsigned long int)(pthread_self()), "none");
#
# // Now write the message proper into buf[],
# // right after the timestamp:
# //
# len = strlen( buf );
#
# // Drop leading blanks:
# //
# while (*fmt == ' ') ++fmt;
#
# va_start(va, fmt);
# vsnprintf(buf+len, MAX_BUF-len, fmt, va);
# va_end(va);
#
# // Append a newline to buffer:
# //
# strcpy( buf + strlen(buf), "\n" );
#
# puts( buf );
# }
#
# int loops = 10;
# int foo = 0;
# int last = 0;
#
#
#
# void set_up_mutex( void ) {
# //
# int err = pthread_mutex_init( &mutex, NULL );
# //
# switch (err) {
# //
# case 0: break;
# case ENOMEM: puts("Insufficient ram to initialize mutex\n"); exit(1);
# case EAGAIN: puts("Insufficient (non-ram) resources to initialize mutex\n"); exit(1);
# case EPERM: puts("Caller lacks privilege to initialize mutex\n"); exit(1);
# case EBUSY: puts("Attempt to reinitialize the object referenced by mutex, a previously initialized, but not yet destroyed, mutex.\n"); exit(1);
# case EINVAL: puts("Invalid attribute\n"); exit(1);
# default: puts("Undocumented error return from pthread_mutex_init()\n"); exit(1);
# }
# }
#
#
#
# void set_up_condvar( void ) {
# //
# int result = pthread_cond_init( &condvar, NULL );
# //
# if (result) { puts("pth__condvar_init: Unable to initialize condition variable.\n"); exit(1); }
# }
#
#
#
# void* subthread_fn( void* id_as_voidptr ) {
# int id = (int) id_as_voidptr; // Ick.
# log_if( "subthread_fn(%d)/AAA", id );
# { int err = pthread_mutex_lock( &mutex );
# switch (err) {
# //
# case 0: break;; // Success.
# case EINVAL: puts("pth__mutex_lock: Invalid mutex or mutex has HOSTTHREAD_PRIO_PROTECT set and calling thread's priority is higher than mutex's priority ceiling.\n"); exit(1);
# case EBUSY: puts("pth__mutex_lock: Mutex was already locked.\n"); exit(1);
# case EAGAIN: puts("pth__mutex_lock: Recursive lock limit exceeded.\n"); exit(1);
# case EDEADLK: puts("pth__mutex_lock: Deadlock, or mutex already owned by thread.\n"); exit(1);
# default: puts("pth__mutex_lock: Undocumented error return from pthread_mutex_lock()\n"); exit(1);
# }
# }
# log_if("subthread_fn(%d)/BBB", id);
# //
# // for (i = 0; i < loops; ++i) {
# log_if("subthread_fn(%d)/CCC", id);
# while (((last + 1) % 4) != id) {
# log_if("subthread_fn(%d)/DDD *last d=%d\n", id, last);
#
# int result = pthread_cond_wait( &condvar, &mutex );
# if (result) { puts("pth__condvar_wait: Unable to wait on condition variable.\n"); exit(1); }
#
# log_if("subthread_fn(%d)/EEE", id);
# };
# log_if("subthread_fn(%d)/FFF", id);
# ++foo;
# log_if("subthread_fn(%d)/GGG", id);
# last = id;
# log_if("subthread_fn(%d)/HHH", id);
# int result = pthread_cond_broadcast( &condvar );
# if (result) { puts("pth__condvar_broadcast: Unable to broadcast on condition variable.\n"); exit(1); }
#
# log_if("subthread_fn(%d)/III", id);
# log_if("%d", id);
# // };
# //
# log_if("subthread_fn(%d)/JJJ", id);
# { int err = pthread_mutex_unlock( &mutex );
# switch (err) {
# //
# case 0: break; // Successfully released lock.
# case EINVAL: puts("pth__mutex_unlock: Mutex has HOSTTHREAD_PRIO_PROTECT set and calling thread's priority is higher than mutex's priority ceiling.\n"); exit(1);
# case EBUSY: puts("pth__mutex_unlock: The mutex already locked.\n"); exit(1);
# default: puts("pth__mutex_unlock: Undocumented error returned by pthread_mutex_unlock().\n"); exit(1);
# }
# }
#
# log_if("subthread_fn(%d)/KKK", id);
# pthread_exit( NULL );
# return (void*) NULL;
# }
#
# pthread_t spawn_subthread( int id ) {
# //
# pthread_t tid;
# //
# int err = pthread_create( &tid, NULL, subthread_fn, (void*)id );
# //
# switch (err) {
# //
# case 0: break;
# case EAGAIN: puts("pth__pthread_create: Insufficient resources to create posix thread: May have reached PTHREAD_THREADS_MAX.\n"); exit(1);
# case EPERM: puts("pth__pthread_create: You lack permissions to set requested scheduling.\n"); exit(1);
# case EINVAL: puts("pth__pthread_create: Invalid attributes.\n"); exit(1);
# default: puts("pth__pthread_create: Undocumented error returned by pthread_create().\n"); exit(1);
# }
# return tid;
# }
#
# void join_subthread( pthread_t subthread_id ) {
# //
# int err = pthread_join( subthread_id, NULL );
# switch (err) {
# //
# case 0: break;
# case ESRCH: puts("pth__pthread_join: No such thread.\n"); exit(1);
# case EDEADLK: puts("pth__pthread_join: Attempt to join self (or other deadlock).\n"); exit(1);
# case EINVAL: puts("pth__pthread_join: Not a joinable thread.\n"); exit(1);
# default: puts("pth__pthread_join: Undocumented error.\n"); exit(1);
# }
# }
#
# int main( int argc, char** argv ) {
# //
# set_up_mutex();
# set_up_condvar();
# log_if("111");
# pthread_t subthread0 = spawn_subthread(0); log_if("222");
# pthread_t subthread1 = spawn_subthread(1); log_if("333");
# pthread_t subthread2 = spawn_subthread(2); log_if("444");
# pthread_t subthread3 = spawn_subthread(3); log_if("555");
#
# join_subthread( subthread0 ); log_if("666");
# join_subthread( subthread1 ); log_if("777");
# join_subthread( subthread2 ); log_if("888");
# join_subthread( subthread3 ); log_if("999");
# //
# printf("z.c: Done!\n");
# exit(0);
# }