PreviousUpNext

15.4.933  src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit-unit-test.pkg

# threadkit-unit-test.pkg 
#
# Unit tests for:
#     src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg

# Compiled by:
#     src/lib/test/unit-tests.lib

# Run by:
#     src/lib/test/all-unit-tests.pkg

stipulate
    include package   threadkit;                                # threadkit                             is from   src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg
    #
    package bx  =  binarytree_ximp;                             # binarytree_ximp                       is from   src/lib/src/lib/thread-kit/src/core-thread-kit/binarytree-ximp.pkg
    package ci  =  mythryl_callable_c_library_interface;        # mythryl_callable_c_library_interface  is from   src/lib/std/src/unsafe/mythryl-callable-c-library-interface.pkg
    package hth =  hostthread;                                  # hostthread                            is from   src/lib/std/src/hostthread.pkg
    package io  =  io_bound_task_hostthreads;                   # io_bound_task_hostthreads             is from   src/lib/std/src/hostthread/io-bound-task-hostthreads.pkg
    package mps =  microthread_preemptive_scheduler;            # microthread_preemptive_scheduler      is from   src/lib/src/lib/thread-kit/src/core-thread-kit/microthread-preemptive-scheduler.pkg
    package psx =  posixlib;                                    # posixlib                              is from   src/lib/std/src/psx/posixlib.pkg
    package tim =  time;                                        # time                                  is from   src/lib/std/time.pkg
herein

    package threadkit_unit_test {

                                                                # unit_test                             is from   src/lib/src/unit-test.pkg
                                                                # threadkit                             is from   src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit.pkg
                                                                # mailslot                              is from   src/lib/src/lib/thread-kit/src/core-thread-kit/mailslot.pkg
                                                                # maildrop                              is from   src/lib/src/lib/thread-kit/src/core-thread-kit/maildrop.pkg
        include package   unit_test;                            # unit_test                             is from   src/lib/src/unit-test.pkg


        nonfix my before;

                                                                                                                                        my _ = log::note {. "threadkit_unit_test/AAA"; };
#       start_up_thread_scheduler  =  tsc::start_up_thread_scheduler;
# my _ = printf "threadkit_unit_test/BBB\n";
#       shut_down_thread_scheduler =  tsc::shut_down_thread_scheduler;
# my _ = printf "threadkit_unit_test/CCC\n";

        name =  "src/lib/src/lib/thread-kit/src/core-thread-kit/threadkit-unit-test.pkg tests";



        fun test_basic_mailslot_functionality_a ()
            =
            {   # Send one message through a mailslot
                # and verify that it is received:


                slot =   make_mailslot ():   Mailslot(Int);

                make_thread  "threadkit_unit_test"  {.
                    #
                    put_in_mailslot (slot, 13);
                    thread_exit { success => TRUE };
                };

                k = take_from_mailslot slot;

                assert (k == 13);
            };


        fun test_basic_mailslot_functionality_b ()
            =
            {   # Send fifty messages through a mailslot
                # and verify that they are received:

                messages_to_transmit =  50;

                #
                Message = NONFINAL_MESSAGE Int
                        |    FINAL_MESSAGE Int
                        ;

                slot =   make_mailslot ():   Mailslot(Message);

                make_thread  "threadkit_unit_test"  {.
                    #
                    for (i = 1;  i < messages_to_transmit;  ++i) {
                        #
                        put_in_mailslot (slot, NONFINAL_MESSAGE i);
                    };

                    put_in_mailslot (slot, FINAL_MESSAGE messages_to_transmit);

                    thread_exit { success => TRUE };
                };

                messages_received
                    =
                    loop 0
                    where
                        fun loop i
                            =
{
                                                                                                                                        log::note {. sprintf "%s\ttest_basic_mailslot_functionality_b/TAKELUP(%d)TOP"  (mps::thread_scheduler_statestring ()) i; };
                                                                                                                                        k = take_from_mailslot slot;
                                                                                                                                        case k   NONFINAL_MESSAGE n =>  log::note {. sprintf "%s\ttest_basic_mailslot_functionality_b/TAKELUP(%d) post-take  NONFINAL_MESSAGE %d"  (mps::thread_scheduler_statestring ())  i  n; };
                                                                                                                                                    FINAL_MESSAGE n =>  log::note {. sprintf "%s\ttest_basic_mailslot_functionality_b/TAKELUP(%d) post-take     FINAL_MESSAGE %d"  (mps::thread_scheduler_statestring ())  i  n; };
                                                                                                                                        esac;
                            case k
#                               case (take_from_mailslot slot)
                                #
                                NONFINAL_MESSAGE n =>   loop (i+1);
                                FINAL_MESSAGE    n =>         i+1;
                            esac;
};
                    end;


                assert (messages_to_transmit == messages_received);
            };


# Next step is to mutate this into a test of put_in_mailslot' and take_from_mailslot'
# instead of put_in_mailslot() and take_from_mailslot():
# (I think we should also increase the loopcount above
# once we strip out the printfs())
        fun test_basic_mailslot_functionality_c ()
            =
            {   # Send fifty messages through a mailslot
                # and verify that they are received:

                messages_to_transmit =  50;

                #
                Message = NONFINAL_MESSAGE Int
                        |    FINAL_MESSAGE Int
                        ;

                slot =   make_mailslot ():   Mailslot(Message);

                make_thread  "threadkit_unit_test"  {.
                    #
                    for (i = 1;  i < messages_to_transmit;  ++i) {
                        #
                        block_until_mailop_fires (put_in_mailslot' (slot, NONFINAL_MESSAGE i));
                    };

                    block_until_mailop_fires (put_in_mailslot' (slot, FINAL_MESSAGE messages_to_transmit));

                    thread_exit { success => TRUE };
                };

                messages_received
                    =
                    loop 0
                    where
                        fun loop i
                            =
{
                                                                                                                                        log::note {. sprintf "%s\ttest_basic_mailslot_functionality_c/TAKELUP(%d)TOP:" (mps::thread_scheduler_statestring ()) i; };
                                                                                                                                        k = block_until_mailop_fires (take_from_mailslot' slot);
                                                                                                                                        case k   NONFINAL_MESSAGE n =>  log::note {. sprintf "%s\ttest_basic_mailslot_functionality_c/TAKELUP(%d) post-take:   NONFINAL_MESSAGE %d"  (mps::thread_scheduler_statestring ())  i  n;  };
                                                                                                                                                    FINAL_MESSAGE n =>  log::note {. sprintf "%s\ttest_basic_mailslot_functionality_c/TAKELUP(%d) post-take:      FINAL_MESSAGE %d"  (mps::thread_scheduler_statestring ())  i  n;  };
                                                                                                                                        esac;
                            case k
#                               case (block_until_mailop_fires (take_from_mailslot' slot))
                                #
                                NONFINAL_MESSAGE n =>   loop (i+1);
                                FINAL_MESSAGE    n =>         i+1;
                            esac;
};
                    end;


                assert (messages_to_transmit == messages_received);
            };


        fun test_basic_maildrop_functionality ()
            =
            { 
                #
                put_to_full_maildrop_should_fail ();

                put_to_empty_maildrop_should_work ();

                get_from_empty_maildrop_should_block ();

                exercise_nonblocking_maildrop_peeks ();

                exercise_blocking_maildrop_peeks ();

                exercise_maildrop_value_swaps ();
            }
            where
                fun put_to_full_maildrop_should_fail ()
                    =
                    {   drop = make_full_maildrop ():   Maildrop(Void);
                        #                           
                        worked = REF FALSE;

                        put_in_maildrop (drop, ())
                        except
                            MAY_NOT_FILL_ALREADY_FULL_MAILDROP
                                =
                                worked := TRUE;

                        assert  *worked;
                    };

                fun put_to_empty_maildrop_should_work ()
                    =
                    {   drop =   make_empty_maildrop ():   Maildrop(Int);
                        #
                        worked = REF TRUE;

                        put_in_maildrop (drop, 17)
                        except
                            MAY_NOT_FILL_ALREADY_FULL_MAILDROP
                                =
                                worked := FALSE;

                        assert  *worked;

                        assert (take_from_maildrop drop  ==  17);
                    };

                fun get_from_empty_maildrop_should_block ()
                    =
                    {   drop1 =   make_empty_maildrop ():   Maildrop(Int);
                        drop2 =   make_empty_maildrop ():   Maildrop(Int);
                        #
                        make_thread  "threadkit_unit_test 2"  {.
                            #
                            put_in_maildrop (drop2, take_from_maildrop drop1 + 1);
                            thread_exit { success => TRUE };
                        };

                        put_in_maildrop (drop1, 23);
                        assert (take_from_maildrop drop2 == 24);
                    };

                fun exercise_nonblocking_maildrop_peeks ()
                    =
                    {   drop =   make_full_maildrop 29:   Maildrop(Int);
                        #                           
                        assert (get_from_maildrop drop == 29);          # Peek at maildrop without emptying it.
                        assert (the (nonblocking_get_from_maildrop drop) == 29);

                        assert (take_from_maildrop  drop == 29);        # Read and empty maildrop.

                        case (nonblocking_get_from_maildrop drop)       # Peek to verify maildrop is now empty.
                            #
                            NULL =>  assert TRUE;
                            _    =>  assert FALSE;
                        esac;
                    };

                fun exercise_blocking_maildrop_peeks ()
                    =
                    {
                        drop1 =   make_empty_maildrop ():   Maildrop(Int);
                        drop2 =   make_empty_maildrop ():   Maildrop(Int);
                        #
                        make_thread  "threadkit_unit_test 3"  {.
                            #
                            v0 = get_from_maildrop drop1;
                            v1 = v0 + 1;
                            put_in_maildrop (drop2, v1);
                            thread_exit { success => TRUE };
                        };

                        put_in_maildrop (drop1, 37);
                        assert (get_from_maildrop drop2 == 38);
                    };

                fun exercise_maildrop_value_swaps ()
                    =
                    {   drop =   make_full_maildrop (57):   Maildrop( Int );
                        #                           
                        assert (maildrop_swap  (drop, 59) == 57);
                        assert (take_from_maildrop  drop      == 59);
                    };
            end;


        fun test_basic_mailqueue_functionality ()
            =
            { 
                #
                get_from_empty_mailqueue_should_block ();

                queue_and_dequeue_50_values ();
                test_take_all  ();
                test_take_all' ();
            }
            where
                fun get_from_empty_mailqueue_should_block ()
                    =
                    {   q1 = make_mailqueue (get_current_microthread()):   Mailqueue(Int);
                        q2 = make_mailqueue (get_current_microthread()):   Mailqueue(Int);
                        #
                        make_thread  "threadkit_unit_test 4" {.
                            #
                            put_in_mailqueue (q2, take_from_mailqueue q1  + 1);
                            thread_exit { success => TRUE };
                        };

                        put_in_mailqueue (q1, 93);
                        assert (take_from_mailqueue q2 == 94);
                    };

                fun queue_and_dequeue_50_values ()
                    =
                    {   messages_to_transmit  =  50;
                        #
                        Message = NONFINAL_MESSAGE | FINAL_MESSAGE;

                        q =   make_mailqueue (get_current_microthread()):   Mailqueue( Message );

                        for (i = 1;   i < messages_to_transmit;  ++i) {
                            #
                            put_in_mailqueue (q, NONFINAL_MESSAGE);
                        };
                        put_in_mailqueue (q, FINAL_MESSAGE);

                        messages_received
                            =
                            loop 0
                            where
                                fun loop i
                                    =
                                    case (take_from_mailqueue  q)
                                        #
                                        NONFINAL_MESSAGE => loop (i+1);
                                           FINAL_MESSAGE =>      (i+1);
                                    esac;
                            end;

                        assert (messages_received == messages_to_transmit);
                    };


                fun test_take_all ()
                    =
                    {   # This is a whitebox test intended to verify that
                        # ordering is correct when we have three items
                        # in the front of the queue and three in the back:
                        #
                        q =   make_mailqueue (get_current_microthread()):   Mailqueue( Int );
                        #
                        for (i = 1;   i <= 4;  ++i) {
                            #
                            put_in_mailqueue (q, i);
                        };

                        assert ((take_from_mailqueue q) == 1);                          # This will force the above values into the front half of queue.

                        for (i = 5;   i <= 7;  ++i) {
                            #
                            put_in_mailqueue (q, i);                                    # These values will go on the back half of queue.
                        };

                        assert ((take_all_from_mailqueue q) == [ 2, 3, 4, 5, 6, 7 ]);   # This call has to concatenate the front and back queue halves correctly.
                    };

                fun test_take_all' ()
                    =
                    {   q =   make_mailqueue (get_current_microthread()):   Mailqueue( Int );
                        #
                        for (i = 1;   i <= 4;  ++i) {
                            #
                            put_in_mailqueue (q, i);
                        };

                        assert (take_from_mailqueue q  ==  1);                          # This will force the above values into the front half of queue.

                        for (i = 5;   i <= 7;  ++i) {
                            #
                            put_in_mailqueue (q, i);                                    # These values will go on the back half of queue.
                        };

                        assert ((block_until_mailop_fires (take_all_from_mailqueue' q)) == [ 2, 3, 4, 5, 6, 7 ]);       # This call has to concatenate the front and back queue halves correctly.
                    };

            end;


        fun test_basic_mailcaster_functionality ()
            =
            { 
                #
                get_from_empty_mailcaster_should_block ();

                queue_and_dequeue_50_values ();
            }
            where
                fun get_from_empty_mailcaster_should_block ()
                    =
                    {   c1 =   make_mailcaster ():   Mailcaster(Int);
                        c2 =   make_mailcaster ():   Mailcaster(Int);

                        q1 =   make_readqueue c1:   Readqueue(Int);
                        q2 =   make_readqueue c2:   Readqueue(Int);

                        make_thread  "threadkit_unit_test 5" {.
                            #
                            transmit (c2, receive q1  + 1);
                            thread_exit { success => TRUE };
                        };

                        transmit (c1, 93);
                        assert (receive q2 == 94);
                    };


                fun queue_and_dequeue_50_values ()
                    =
                    {   messages_to_transmit  =  50;
                        #
                        Message = NONFINAL_MESSAGE | FINAL_MESSAGE;


                        # Create a mailcaster and two readqueues on it:

                        c =   make_mailcaster ():   Mailcaster( Message );
                            

                        q1 =   make_readqueue  c:   Readqueue( Message );
                        q2 =   make_readqueue  c:   Readqueue( Message );


                        # Write 50 messages into mailcaster:
                        #
                        for (i = 1;   i < messages_to_transmit;  ++i) {
                            #
                            transmit (c, NONFINAL_MESSAGE);
                        };
                        transmit (c, FINAL_MESSAGE);


                        # Read all 50 from first readqueue:
                        #
                        messages_received
                            =
                            loop 0
                            where
                                fun loop i
                                    =
                                    case (receive  q1)
                                        NONFINAL_MESSAGE => loop (i+1);
                                           FINAL_MESSAGE =>      (i+1);
                                    esac;
                            end;

                        assert (messages_received == messages_to_transmit);



                        # Read all 50 from second readqueue:
                        #
                        messages_received
                            =
                            loop 0
                            where
                                fun loop i
                                    =
                                    case (receive  q2)
                                        NONFINAL_MESSAGE => loop (i+1);
                                           FINAL_MESSAGE =>      (i+1);
                                    esac;
                            end;

                        assert (messages_received == messages_to_transmit);
                    };

            end;

        fun test_basic_thread_local_property_functionality ()
            =
            { 
                #
                test_generic_thread_local_property_functionality ();
                test_boolean_thread_local_property_functionality ();
            }
            where
                fun test_generic_thread_local_property_functionality ()
                    =
                    {   prop =  make_per_thread_property {. 0; };
                        #
                        Message = ONE(Int) | TWO(Int);

                        slot =   make_mailslot ():   Mailslot( Message );
                            

                        make_thread  "threadkit_unit_test 6" {.
                            #
                            prop.set 1;
                            put_in_mailslot (slot, ONE (prop.get ()));
                        };

                        make_thread  "threadkit_unit_test 7" {.
                            #
                            prop.set 2;
                            put_in_mailslot (slot, TWO (prop.get ()));
                        };

                        case (take_from_mailslot  slot)
                            #
                            ONE one =>  assert (one == 1);
                            TWO two =>  assert (two == 2);
                        esac;

                        case (take_from_mailslot  slot)
                            #
                            ONE one =>  assert (one == 1);
                            TWO two =>  assert (two == 2);
                        esac;
                    };

                fun test_boolean_thread_local_property_functionality ()
                    =
                    {   prop =  make_boolean_per_thread_property ();
                        #
                        Message =  TRUE_MESSAGE Bool
                                | FALSE_MESSAGE Bool
                                ;

                        slot =  make_mailslot ():   Mailslot( Message );

                        make_thread  "threadkit_unit_test 8" {.
                            #
                            prop.set TRUE;
                            put_in_mailslot (slot, TRUE_MESSAGE (prop.get ()));
                        };

                        make_thread  "threadkit_unit_test 9" {.
                            #
                            prop.set FALSE;
                            put_in_mailslot (slot, FALSE_MESSAGE (prop.get ()));
                        };

                        case (take_from_mailslot slot)
                            #
                             TRUE_MESSAGE  true_val =>  assert ( true_val == TRUE );
                            FALSE_MESSAGE false_val =>  assert (false_val == FALSE);
                        esac;

                        case (take_from_mailslot slot)
                            #
                             TRUE_MESSAGE  true_val =>  assert ( true_val == TRUE );
                            FALSE_MESSAGE false_val =>  assert (false_val == FALSE);
                        esac;
                    };
            end;

        fun test_basic_timeout_functionality ()
            =
            { 
                #
                test_sleep_for   ();
                test_sleep_until ();
            }
            where
                now    =  tim::get_current_time_utc;
                fun test_sleep_for ()
                    =
                    {
                        before =  now ();
                        #
                        sleep_for 0.1;

                        after  =   now ();

                        elapsed_time = tim::(-) (after, before);
                        milliseconds = tim::to_milliseconds  elapsed_time;

                        assert (milliseconds >= 100); 
                    };

                fun test_sleep_until ()
                    =
                    {
                        before       =  now ();
                        wakeup_time  =  tim::(+) (before, tim::from_milliseconds 100);

                        sleep_until  wakeup_time;

                        after        =  now ();

                        assert (tim::(>=) (after, wakeup_time));
                    };

            end;

        fun test_basic_select_functionality ()
            =
            { 
                #
                test_select_over_input_mailslots  ();
                test_select_over_input_maildrops  ();
                test_select_over_input_mailqueues ();

                test_select_over_output_mailslots  ();

                test_select_over_timeout_mailops ();
            }
            where
                fun test_select_over_input_mailslots ()
                    =
                    {
                        input_slot_1 =   make_mailslot ():   Mailslot(Int);
                        input_slot_2 =   make_mailslot ():   Mailslot(Int);

                        output_drop_1 =  make_empty_maildrop ():   Maildrop(Int);
                        output_drop_2 =  make_empty_maildrop ():   Maildrop(Int);

                        make_thread  "threadkit_unit_test 10" {.
                            #
                            for (;;) {
                                #
                                do_one_mailop [
                                    take_from_mailslot'  input_slot_1  ==>  {. 
                                                                 put_in_maildrop (output_drop_1, #value); },
                                    take_from_mailslot'  input_slot_2  ==>  {. 
                                                                 put_in_maildrop (output_drop_2, #value); }
                                ];
                            };
                        };

                        put_in_mailslot (input_slot_1, 13);
                        put_in_mailslot (input_slot_2, 17);

                        assert (take_from_maildrop  output_drop_1  ==  13); 
                        assert (take_from_maildrop  output_drop_2  ==  17); 
                    };


                fun test_select_over_input_maildrops ()
                    =
                    {   input_drop_1  =  make_empty_maildrop ():  Maildrop(Int);
                        input_drop_2  =  make_empty_maildrop ():  Maildrop(Int);
                                                                  
                        output_drop_1 =  make_empty_maildrop ():  Maildrop(Int);
                        output_drop_2 =  make_empty_maildrop ():  Maildrop(Int);

                        make_thread  "threadkit_unit_test 11" {.
                            #
                            for (;;) {
                                #
                                do_one_mailop [
                                    take_from_maildrop'  input_drop_1  ==>  {. 
                                                                  put_in_maildrop (output_drop_1, #value); },
                                    take_from_maildrop'  input_drop_2  ==>  {. 
                                                                  put_in_maildrop (output_drop_2, #value); }
                                ];
                            };
                        };

                        put_in_maildrop (input_drop_1, 11);
                        put_in_maildrop (input_drop_2, 19);

                        assert (take_from_maildrop  output_drop_1  ==  11); 
                        assert (take_from_maildrop  output_drop_2  ==  19); 
                    };


                fun test_select_over_input_mailqueues ()
                    =
                    {   input_queue_1 =  make_mailqueue (get_current_microthread()):  Mailqueue(Int);
                        input_queue_2 =  make_mailqueue (get_current_microthread()):  Mailqueue(Int);

                        output_drop_1 =  make_empty_maildrop (): Maildrop(Int);
                        output_drop_2 =  make_empty_maildrop (): Maildrop(Int);

                        make_thread  "threadkit_unit_test 12" {.
                            #
                            for (;;) {
                                #
                                do_one_mailop [
                                    take_from_mailqueue'  input_queue_1  ==>  {. 
                                                                  put_in_maildrop (output_drop_1, #value); },
                                    take_from_mailqueue'  input_queue_2  ==>  {. 
                                                                  put_in_maildrop (output_drop_2, #value); }
                                ];
                            };
                        };

                        put_in_mailqueue (input_queue_1, 1);
                        put_in_mailqueue (input_queue_2, 3);

                        assert (take_from_maildrop  output_drop_1  ==  1); 
                        assert (take_from_maildrop  output_drop_2  ==  3); 
                    };


                fun test_select_over_output_mailslots ()
                    =
                    {   output_slot_1 =  make_mailslot ():   Mailslot(Int);
                        output_slot_2 =  make_mailslot ():   Mailslot(Int);

                        make_thread  "threadkit_unit_test 13" {.
                            #
                            for (;;) {
                                #
                                do_one_mailop [
                                    put_in_mailslot'  (output_slot_1, 3)  ==>  {. (); },
                                    put_in_mailslot'  (output_slot_2, 5)  ==>  {. (); }
                                ];
                            };
                        };

                        assert (take_from_mailslot  output_slot_1  ==  3);
                        assert (take_from_mailslot  output_slot_2  ==  5);
                    };


                fun test_select_over_timeout_mailops ()
                    =
                    {   output_slot =   make_mailslot ():   Mailslot(Int);

                        make_thread  "threadkit_unit_test 14" {.
                            #
                            do_one_mailop [
                                timeout_in' 0.100  ==>  {. put_in_mailslot (output_slot, 100); },
                                timeout_in' 0.050  ==>  {. put_in_mailslot (output_slot,  50); },
                                timeout_in' 0.010  ==>  {. put_in_mailslot (output_slot,  10); }
                            ];

                            do_one_mailop [
                                timeout_in' 0.100  ==>  {. put_in_mailslot (output_slot, 100); },
                                timeout_in' 0.050  ==>  {. put_in_mailslot (output_slot,  50); }
                            ];

                            do_one_mailop [
                                timeout_in' 0.100  ==>  {. put_in_mailslot (output_slot, 100); }
                            ];
                        };

                        assert (take_from_mailslot  output_slot  ==  10);
                        assert (take_from_mailslot  output_slot  ==  50);
                        assert (take_from_mailslot  output_slot  == 100);
                    };


            end;

        fun test_basic_succeed_vs_fail_functionality ()
            =
            {
                test_exit_state_of_thread_that_succeeded ();
                test_exit_state_of_thread_that_failed ();
                test_exit_state_of_thread_killed_by_exception ();

                test_exit_state_of_task_that_succeeded ();
                test_exit_state_of_task_that_failed ();
                test_exit_state_of_task_killed_by_exception ();

                test_exit_state_of_2_thread_task_that_succeeded ();
                test_exit_state_of_2_thread_task_that_failed_a ();
                test_exit_state_of_2_thread_task_that_failed_b ();
                test_exit_state_of_2_thread_task_that_failed_c ();
                test_exit_state_of_2_thread_task_killed_by_exception_a ();
                test_exit_state_of_2_thread_task_killed_by_exception_b ();

                test_exit_state_of_thread_killed_as_successful ();
                test_exit_state_of_thread_killed_as_failure ();
            }
            where
                fun test_exit_state_of_thread_that_succeeded ()
                    =
                    {
                        test_thread      =  make_thread "successful thread" {. thread_exit { success => TRUE }; };
                        thread_finished' =  thread_done__mailop  test_thread;

                        block_until_mailop_fires  thread_finished';

                        assert (get_thread's_state  test_thread  ==  state::SUCCESS);
                    };

                fun test_exit_state_of_thread_that_failed ()
                    =
                    {
                        test_thread      =  make_thread "unsuccessful thread" {. thread_exit { success => FALSE }; };
                        thread_finished' =  thread_done__mailop  test_thread;

                        block_until_mailop_fires  thread_finished';

                        assert (get_thread's_state  test_thread  ==  state::FAILURE);
                    };

                fun test_exit_state_of_thread_killed_by_exception ()
                    =
                    {
                        printf "\nThe following DIE exception is a test -- IGNORE IT: ";
                        test_thread      =  make_thread "exceptional thread" {. raise exception DIE "testing..."; };
                        thread_finished' =  thread_done__mailop  test_thread;

                        block_until_mailop_fires  thread_finished';

                        assert (get_thread's_state  test_thread  ==  state::FAILURE_DUE_TO_UNCAUGHT_EXCEPTION);
                    };


                fun test_exit_state_of_task_that_succeeded ()
                    =
                    {
                        test_task      =  make_task "successful task" [  ("successful thread",  {. thread_exit { success => TRUE }; } )  ];
                        task_finished' =  task_done__mailop  test_task;

                        block_until_mailop_fires  task_finished';

                        assert (get_task's_state  test_task  ==  state::SUCCESS);
                    };

                fun test_exit_state_of_task_that_failed ()
                    =
                    {
                        test_task      =  make_task "unsuccessful task" [  ("unsuccessful thread",  {. thread_exit { success => FALSE }; } )  ];
                        task_finished' =  task_done__mailop  test_task;

                        block_until_mailop_fires  task_finished';

                        assert (get_task's_state  test_task  ==  state::FAILURE);
                    };

                fun test_exit_state_of_task_killed_by_exception ()
                    =
                    {
                        printf "\nThe following DIE exception is a test -- IGNORE IT: ";
                        test_task      =  make_task "exceptional task"  [  ("exceptional thread",  {. raise exception DIE "testing..."; } )  ];
                        task_finished' =  task_done__mailop  test_task;

                        block_until_mailop_fires  task_finished';

                        assert (get_task's_state  test_task  ==  state::FAILURE_DUE_TO_UNCAUGHT_EXCEPTION);
                    };


                fun test_exit_state_of_2_thread_task_that_succeeded ()
                    =
                    {
                        test_task      =  make_task "successful task" [  ("successful thread",  {. thread_exit { success => TRUE }; } ),
                                                                         ("successful thread",  {. thread_exit { success => TRUE }; } )
                                                                      ];
                        task_finished' =  task_done__mailop  test_task;

                        block_until_mailop_fires  task_finished';

                        assert (get_task's_state  test_task  ==  state::SUCCESS);
                    };

                fun test_exit_state_of_2_thread_task_that_failed_a ()
                    =
                    {
                        test_task      =  make_task "unsuccessful task" [  ("unsuccessful thread",  {. thread_exit { success => FALSE }; } ),
                                                                           ("unsuccessful thread",  {. thread_exit { success => FALSE }; } )
                                                                        ];
                        task_finished' =  task_done__mailop  test_task;

                        block_until_mailop_fires  task_finished';

                        assert (get_task's_state  test_task  ==  state::FAILURE);
                    };

                fun test_exit_state_of_2_thread_task_that_failed_b ()
                    =
                    {
                        test_task      =  make_task "unsuccessful task" [  ("unsuccessful thread",  {. thread_exit { success => FALSE }; } ),
                                                                           ("unsuccessful thread",  {. thread_exit { success => TRUE  }; } )
                                                                        ];
                        task_finished' =  task_done__mailop  test_task;

                        block_until_mailop_fires  task_finished';

                        assert (get_task's_state  test_task  ==  state::FAILURE);
                    };

                fun test_exit_state_of_2_thread_task_that_failed_c ()
                    =
                    {
                        test_task      =  make_task "unsuccessful task" [  ("unsuccessful thread",  {. thread_exit { success => TRUE  }; } ),
                                                                           ("unsuccessful thread",  {. thread_exit { success => FALSE }; } )
                                                                        ];
                        task_finished' =  task_done__mailop  test_task;

                        block_until_mailop_fires  task_finished';

                        assert (get_task's_state  test_task  ==  state::FAILURE);
                    };

                fun test_exit_state_of_2_thread_task_killed_by_exception_a ()
                    =
                    {
                        printf "\nThe following DIE exception is a test -- IGNORE IT: ";
                        test_task      =  make_task "exceptional task"  [  ("exceptional thread",  {. raise exception DIE "testing..."; } ),
                                                                           ("successful thread",   {. thread_exit { success => TRUE }; } )
                                                                        ];
                        task_finished' =  task_done__mailop  test_task;

                        block_until_mailop_fires  task_finished';

                        assert (get_task's_state  test_task  ==  state::FAILURE_DUE_TO_UNCAUGHT_EXCEPTION);
                    };

                fun test_exit_state_of_2_thread_task_killed_by_exception_b ()
                    =
                    {
                        printf "\nThe following DIE exception is a test -- IGNORE IT: ";
                        test_task      =  make_task "exceptional task"  [  ("successful thread",   {. thread_exit { success => TRUE }; } ),
                                                                           ("exceptional thread",  {. raise exception DIE "testing..."; } )
                                                                        ];
                        task_finished' =  task_done__mailop  test_task;

                        block_until_mailop_fires  task_finished';

                        assert (get_task's_state  test_task  ==  state::FAILURE_DUE_TO_UNCAUGHT_EXCEPTION);
                    };

                fun test_exit_state_of_thread_killed_as_successful ()
                    =
                    {
                        test_thread      =  make_thread "infinite-loop thread" {. fun loop () = loop (); loop (); };
                        thread_finished' =  thread_done__mailop  test_thread;

                        yield ();
                        yield ();
                        yield ();

                        assert (get_thread's_state  test_thread  ==  state::ALIVE);

                        kill_thread  {  thread => test_thread,  success => TRUE  };

                        block_until_mailop_fires  thread_finished';

                        assert (get_thread's_state  test_thread  ==  state::SUCCESS);
                    };

                fun test_exit_state_of_thread_killed_as_failure ()
                    =
                    {
                        test_thread      =  make_thread "infinite-loop thread" {. loop () where fun loop () = loop (); end; };
                        thread_finished' =  thread_done__mailop  test_thread;

                        yield ();
                        yield ();
                        yield ();

                        assert (get_thread's_state  test_thread  ==  state::ALIVE);

                        kill_thread  {  thread => test_thread,  success => FALSE  };

                        block_until_mailop_fires  thread_finished';

                        assert (get_thread's_state  test_thread  ==  state::FAILURE);
                    };
            end;


        fun test_basic_preemptive_scheduling_fairness ()
            =
            {
                # Run two CPU-bound-loop microthreads for one second
                # and verify that they both get a fair share of cycles:

                mps::alarm_handler_calls                                        :=  0;
                mps::alarm_handler_calls_with__uninterruptible_scope_mutex__set :=  0;
                mps::alarm_handler_calls_with__microthread_switch_lock__set     :=  0;

                stipulate
                    dummy = REF 0;
                herein
                    fun worker_thread counter
                        =
                        {   for (i = 100000; i > 0; --i) {
                                if (i & 1 == 0)   dummy := *dummy + 1;                  # This is just intended to discourage the compiler from optimizing the loop away.
                                else              dummy := *dummy - 1;
                                fi;
                            };
                            counter := *counter + 1;                                    # Track 'work' done by microthread.
                            worker_thread counter;      
                        };
                end;


                # Counters to track 'work' done by the two microthreads:
                #
                counter1 = REF 0;
                counter2 = REF 0;

                fun initialize__timeslicing__task ()
                    =
                    {   make_thread "worker thread" {. worker_thread  counter1; };
                        make_thread "worker thread" {. worker_thread  counter2; };
                        #
                        thread_exit { success => TRUE };
                    };

                task =  make_task "Test microthread pre-emptive timeslicing"  [ ("startup_thread", initialize__timeslicing__task) ];

                sleep_for 1.0;                                                          # Let the two worker threads run for a second.
                kill_task { task, success => TRUE };                                    # Shut down the worker threads:

                assert (*mps::alarm_handler_calls > 30);                                # We usually timeslice at 50Hz, so we expect alarm_handler to have been called about 50 times.

                assert (*counter1 > 0);                                                 # We expect both worker threads to have done at least one work unit.
                assert (*counter2 > 0);                                                 # This also guarantees a trouble report when next test cannot run due to divide-by-zero.

                if (*counter1 > 0
                or  *counter2 > 0)
                    #
                    # Compute lesser/greater ratio, which will be
                    # between 0.0 and 1.0 inclusive:
                    #
                    i2f = eight_byte_float::from_int;
                    #
                    f1  =  i2f *counter1;
                    f2  =  i2f *counter2;
                    #
                    ratio = (f1 > f2)  ??  f2 / f1                                      # Avoid divide-by-zero if (only) one of the counts is zero.
                                       ::  f1 / f2;

                    assert (ratio > 0.1);                                               # We expect about 1.0.  If one thread got < 10% as many CPU cycles as the other something is definitely wedged.
                fi;
            };

        fun test_basic_microthread_switch_lock_functionality ()
            =
            {
                # This refcell gets incremented when the primary hostthread
                # (the one running microthread-preemptive-scheduler.pkg)
                # acquires a hostthread-level mutex, and decremented when
                # it releases it.
                #
                assert (*runtime::microthread_switch_lock_refcell__global == 0);        # Should be zero initially.
                
                mutex = hth::make_mutex (); 

                hth::acquire_mutex mutex; 

                assert (*runtime::microthread_switch_lock_refcell__global == 1);        # Should be one now.

                hth::release_mutex mutex;

                assert (*runtime::microthread_switch_lock_refcell__global == 0);        # Should be back to zero now.


                # Doing the same thing in any other hostthread should
                # result in the counter NOT incrementing:

                # [ LATER ]:  I've commented out this test because it
                # fails a few percent of the times it is run.
                #
                # I doubt this is a bug in the implementation, which
                # is quite simple;  I presume it is because background
                # I/O processing can result in
                #     runtime::microthread_switch_lock_refcell__global
                # just happening to be set when we make our test.
                #
                # I don't see an easy fix, and I don't see the game
                # as being worth the candle, so I've just commented it out.
                #
                # If you have a good replacement test for this, by all means
                # post it to the list or email it to me! :-)

#               maildrop =   make_empty_maildrop (): Maildrop(Int);
#
#               io::do  {.
#
#                   hth::acquire_mutex mutex;                                           # Acquire mutex in a different hostthread.
#
#                   i = *runtime::microthread_switch_lock_refcell__global;              # Check lock refcell.
#
#                   hth::release_mutex mutex;                                           # Release mutex.
#
#                   mps::do {.
#                       put_in_maildrop   (maildrop, i);                                # Phone result back to home base.
#                   };
#               }; 
#
#               i = take_from_maildrop  maildrop;                                       # Get phoned result.
#
#               assert (i == 0);

                hth::free_mutex mutex;
            };

        fun run_perfect_number_loop_torture_test ()
            =
            loop (1, 2)                 # Change '2' to 10000 or such for an actual torture test.
# {
# printf "8";
# # log::note_on_stderr {. "run_perfect_number_loop_torture_test/AAA -- threadkit-unit-test.pkg\n"; };
#           loop (1, 2);                # Change '2' to 10000 or such for an actual torture test.
# printf "9";
# # log::note_on_stderr {. "run_perfect_number_loop_torture_test/ZZZ -- threadkit-unit-test.pkg\n"; };
# }
            where
                # Compute perfect numbers in a loop, delegating the inner loop
                # to a secondary hostthread.  This was hanging originally due
                # to improper locking at the microthread/hostthread interface.
                #
                # The default (1, 2) parameters above do not constitute a
                # torture test; we compile and run here just as protection
                # against bitrot.
                #
                fun io_do (task: Void -> Void) = {                                                      hth::acquire_mutex  io::mutex;
                    io::external_request_queue :=  (io::DO_TASK task)  !  *io::external_request_queue; 
                                                                                                        hth::release_mutex io::mutex;  
                                                                                                        hth::broadcast_condvar  io::condvar;  
                };           

                fun mps_do  (thunk: Void -> Void)
                    = 
                    {                                                                                   hth::acquire_mutex mps::mutex;  
                            mps::request_queue :=  (mps::DO_THUNK thunk)  !  *mps::request_queue; 
                                                                                                        hth::release_mutex mps::mutex;  
                                                                                                        hth::broadcast_condvar mps::condvar;  
                    };           


                fun is_perfect_number n
                    =
                    {   sum_of_nonself_factors
                            =
                            for (i = 1, sum = 0;  i < n;  ++i;  sum) {
                                #
                                sum =   (n % i == 0)   ??   (sum + i)   ::   sum;
                            };

                        n == sum_of_nonself_factors;
                    };

                maildrop =   make_empty_maildrop (): Maildrop(Bool);

                fun loop (i, c)
                    =
                    { 
                        io_do  {.
                            b = is_perfect_number i;                                                    # Do most of the work in a secondary hostthread.
                            mps_do {.
                                put_in_maildrop   (maildrop, b);                                        # Send result back to main hostthread.
                            };
                        }; 

                        if (take_from_maildrop  maildrop)                                               # Receive result in main hostthread.
                            #
                            assert (i == 8128  or                                                       # Verify that numbers reported as perfect actually are.
                                    i ==  496  or
                                    i ==   28  or
                                    i ==    6);

#                           printf "%b is perfect!\n" i;                                                # So you can see the 110 11100 ... binary structure.
                        fi;

                        #
                        if (i == 1)  if (c > 1)   loop (10000, c - 1);   fi;
                        else     loop (i - 1, c  );
                        fi;
                    };

            end;

        fun test_basic_ximp_functionality ()
            =
            {   (make_run_gun  ())  ->   { run_gun',  fire_run_gun  };
                (make_end_gun ())  ->   { end_gun', fire_end_gun };

                # Create a binary tree looking like
                #
                #       1
                #      / \
                #     2   3
                #    / \   \
                #   4   5   6
                #      /
                #     7
                #
                bxegg1 =  bx::make_binarytree_egg (1,[]);       (bxegg1 ()) ->  (bxports1, bxegg1');            bx1 =  bxports1.binarytree_port;
                bxegg2 =  bx::make_binarytree_egg (2,[]);       (bxegg2 ()) ->  (bxports2, bxegg2');            bx2 =  bxports2.binarytree_port;
                bxegg3 =  bx::make_binarytree_egg (3,[]);       (bxegg3 ()) ->  (bxports3, bxegg3');            bx3 =  bxports3.binarytree_port;
                bxegg4 =  bx::make_binarytree_egg (4,[]);       (bxegg4 ()) ->  (bxports4, bxegg4');            bx4 =  bxports4.binarytree_port;
                bxegg5 =  bx::make_binarytree_egg (5,[]);       (bxegg5 ()) ->  (bxports5, bxegg5');            bx5 =  bxports5.binarytree_port;
                bxegg6 =  bx::make_binarytree_egg (6,[]);       (bxegg6 ()) ->  (bxports6, bxegg6');            bx6 =  bxports6.binarytree_port;
                bxegg7 =  bx::make_binarytree_egg (7,[]);       (bxegg7 ()) ->  (bxports7, bxegg7');            bx7 =  bxports7.binarytree_port;


                # This is a very handy little debug fn
                # which is used by scattering lines like
                #
                #    if *log::debugging log::note_on_stderr {. sprintf "%s\ttest_basic_ximp_functionality/CCC1 -- configuring node 1" (log::debug_statestring()); };   fi;
                #
                # through the code in question and then to enable them doing
                #
                #    log::debugging := TRUE;
                #
                # fun debug_statestring ()
                #     =
                #     sprintf "%26s %s\t%s   %s/%s   %s/%s"
                #            (log::get_current_microthread's_name())
                #            (mps::thread_scheduler_statestring())
                #            (maildrop_to_string (run_gun,"gun"))
                #            (mailqueue_to_string ((bx::clientport_to_mailqueue bx5),"bx5"))   (replyqueue_to_string (bxports5.replyqueue,"bx5"))
                #            (mailqueue_to_string ((bx::clientport_to_mailqueue bx7),"bx7"))   (replyqueue_to_string (bxports7.replyqueue,"bx7"))
                #     ;
                #
                # log::debug_statestring__hook := debug_statestring;

                # Wire up imp our imps:
                #
                bxegg1' ({ leftkid => THE bx2, rightkid => THE bx3 }, run_gun', end_gun' );
                bxegg2' ({ leftkid => THE bx4, rightkid => THE bx5 }, run_gun', end_gun' );
                bxegg3' ({ leftkid => NULL,    rightkid => THE bx6 }, run_gun', end_gun' );
                bxegg4' ({ leftkid => NULL,    rightkid => NULL    }, run_gun', end_gun' );
                bxegg5' ({ leftkid => THE bx7, rightkid => NULL    }, run_gun', end_gun' );
                bxegg6' ({ leftkid => NULL,    rightkid => NULL    }, run_gun', end_gun' );
                bxegg7' ({ leftkid => NULL,    rightkid => NULL    }, run_gun', end_gun' );

                fire_run_gun ();                                                                        # Start all app imps running.

                assert ((bx4.get_subtree_sum ()) ==  4);                                                # Many calls like this over lifetime of imp.
                assert ((bx6.get_subtree_sum ()) ==  6);                                                # Many calls like this over lifetime of imp.
                assert ((bx7.get_subtree_sum ()) ==  7);                                                # Many calls like this over lifetime of imp.
                assert ((bx3.get_subtree_sum ()) ==  9);                                                # Many calls like this over lifetime of imp.
                assert ((bx5.get_subtree_sum ()) == 12);                                                # Many calls like this over lifetime of imp.
                assert ((bx2.get_subtree_sum ()) == 18);                                                # Many calls like this over lifetime of imp.
                assert ((bx1.get_subtree_sum ()) == 28);                                                # Many calls like this over lifetime of imp.

                fire_end_gun ();                                                                        # Have all app imps shut down.
            };

        fun run ()
            =
            {   printf  "\nDoing %s:\n"  name;
                #
                test_basic_mailslot_functionality_c ();
                test_basic_mailslot_functionality_a ();
                test_basic_mailslot_functionality_b ();
                test_basic_maildrop_functionality ();
                test_basic_mailqueue_functionality ();
                test_basic_mailcaster_functionality ();
                test_basic_thread_local_property_functionality ();
                test_basic_timeout_functionality ();
                test_basic_select_functionality ();
                test_basic_succeed_vs_fail_functionality ();
                test_basic_preemptive_scheduling_fairness ();
                test_basic_microthread_switch_lock_functionality ();
                run_perfect_number_loop_torture_test ();
                test_basic_ximp_functionality ();

                summarize_unit_tests  name;
            };
    };
end;

## Code by Jeff Prothero: Copyright (c) 2010-2015,
## released per terms of SMLNJ-COPYRIGHT.



Comments and suggestions to: bugs@mythryl.org

PreviousUpNext