/*
 *  unittest.sli
 *
 *  This file is part of NEST.
 *
 *  Copyright (C) 2004 The NEST Initiative
 *
 *  NEST 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.
 *
 *  NEST is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with NEST.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% NEST Library for the testsuite
%%
%% (C) 2007-2012 The NEST Collaboration
%%        
%%  Authors   Jochen Martin Eppler
%%            eppler@biologie.uni-freiburg.de
%%            Markus Diesmann
%%            diesmann@brain.riken.jp
%%            Hans Ekkehard Plesser
%%            hans.ekkehard.plesser@umb.no
%%

/unittest ($Revision: 9990 $) provide
/unittest namespace


/* BeginDocumentation
Name: unittest::assert_or_die - Check condition and quit with exit code 1 if it fails

Synopsis: {condition}        assert_or_die -> -
          bool               assert_or_die -> -
          {condition} string assert_or_die -> -
          bool        string assert_or_die -> -

Diagnostics:
This function will quit nest if condition is false (exit code 1),
or if the condition raises an error (exit code 2).
It will print an error message to cerr, too.
If a string argument is given, it will be included in the message.

Examples:
/unittest (7378) require
/unittest using
  {1 1 eq} assert_or_die
  {1 0 eq} (1 != 0) assert_or_die
  {1 /hello add 1 eq} assert_or_die
endusing

Author: R. Kupper, J. M. Eppler

FirstVersion: 07/30/2007

Availability: SLI-2.0

SeeAlso: unittest::fail_or_die, unittest::pass_or_die, assert, quit
*/

/assert_or_die[/booltype]
{
  not
  {
    M_FATAL (unittest::assert_or_die)
    (Assertion failed) 
    /die_message lookup { (: ) exch join join } if
    (.) join message

    M_FATAL (unittest::assert_or_die)
    (Exiting with code 1.) message
    1 quit_i
  } if
} bind def

/assert_or_die[/proceduretype]
{
 << exch /func exch >>
 begin

   /func load pass_or_die
   % check if the condition returned a boolean:   
   dup type /booltype eq not
   {
      M_FATAL (unittest::assert_or_die)
      (Expression did not resolve to a boolean)
      /die_message lookup { (: ) exch join join } if
      (.\nExpression:\n) join /func load pcvs join  message

      M_FATAL (unittest::assert_or_die)
     (Exiting with code 2.) message

     2 quit_i    
   } if
   
   not
   {
     M_FATAL (unittest::assert_or_die)
     (Assertion failed)     
     /die_message lookup { (: ) exch join join } if
     (.\nAssertion:\n) join /func load pcvs join  message

     M_FATAL (unittest::assert_or_die)
     (Exiting with code 1.) message

     1 quit_i
  } if

 end
} bind def

/assert_or_die[/booltype /stringtype]
{
  << >> 
  begin
    /die_message Set
    assert_or_die
  end
} bind def

/assert_or_die[/proceduretype /stringtype]
{
  << >> 
  begin
    /die_message Set
    assert_or_die
  end
} bind def

/* BeginDocumentation
Name: unittest::pass_or_die - Execute a code block and quit with exit code 2 if it fails.

Synopsis: {code}        pass_or_die -> -
          {code} string pass_or_die -> -

Diagnostics:
This function will quit nest if the code block raises any error.
It will print an error message to cerr, too. If string is given, it will be 
included in the error message.

Examples:
/unittest (7378) require
/unittest using
  {1 1 add} pass_or_die
  {1 /hello add} (testing /hello) pass_or_die
endusing

Author: R. Kupper

FirstVersion: 2-jul-2008

Availability: SLI-2.0

SeeAlso: unittest::assert_or_die, unittest::fail_or_die, stopped, assert, quit
*/

/pass_or_die[/proceduretype]
{
  << exch /func exch >>
  begin

  /func load stopped
  {
    handleerror    

    M_FATAL (unittest::pass_or_die)
    (Code block did not pass)
    /die_message lookup { (: ) exch join join } if
    (.\nCode block:\n) join /func load pcvs join  message

    M_FATAL (unittest::pass_or_die) (Exiting with code 2.) message
    2 quit_i
  } if
  end
} bind def

/pass_or_die[/proceduretype /stringtype]
{
  << >> 
  begin
    /die_message Set
    pass_or_die
  end
} bind def



/* BeginDocumentation
Name: unittest::fail_or_die - Execute a code block and exit with exit code 3 if it does not raise an error.

Synopsis: { code } fail_or_die -> -
          { code } string fail_or_die -> -

Description:
This procedure is provided to test that certain errors are raised, e.g., when
illegal values are set.

Diagnostics:
This function will quit nest if the code does NOT raise an error 
(exit code 3). It will print an error message to cerr, too.

Examples:
/unittest (7391) require
/unittest using
  { 1 0 div } fail_or_die    
  { 0 << /resolution 0 >> SetStatus} fail_or_die
endusing

Author: H. E. Plesser

FirstVersion: 07/02/2008

Availability: SLI-2.0

SeeAlso: unittest::assert_or_die, unittest::pass_or_die, assert, quit
*/
/fail_or_die[/proceduretype]
{
  << exch /func exch >>
  begin

    mark % to remove debris from stack after error

    /func load stopped not  % got a problem if we were NOT stopped
    {
      M_FATAL (unittest::fail_or_die)
      (Code block failed to raise an error)
      /die_message lookup { (: ) exch join join } if
      (.\nCode block:\n) join /func load pcvs join  message

      M_FATAL (unittest::fail_or_die) (Exiting with code 3.) message

      3 quit_i
    } 
    if

    % clear errordict, gleaned from /handleerror
    errordict /message undef
    errordict /command undef
    errordict begin /newerror false def end

    % clear stack
    counttomark npop pop  % need to mark separately
  end

} bind def 

/fail_or_die[/proceduretype /stringtype]
{
  << >> 
  begin
    /die_message Set
    fail_or_die
  end
} bind def


/* BeginDocumentation
Name: unittest::failbutnocrash_or_die - Execute a code block and exit with exit code 3 if it does not raise a scripterror

Synopsis: { code } [string] failbutnocrash_or_die -> -

Description:
The procedure tests that a specific error, a scripterror, is raised. An example
is a call of operator add in a situation where the operand stack is empty. add
raises a StackUnderflow error in this case which is a scripterror and the desired
behavior. There are several alternative and undesired behaviors of the code block
in question. add may return without raising an error, this is not the expected behavior
and therefore failbutnocrash_or_die reports a problem. However, other scenarios 
are that the code block causes an assertion to fail or that it causes a segmentation
fault. In contrast to fail_or_die, failbutnocrash_or_die survives such a crash of
the code block to be tested. This is achieved by probing the code block in a separate
nest instance and inspecting the result. The procedure uses the symbolic exit codes
defined in statusdict::exitcodes, scripterror is one of them.

Diagnostics:
This function will quit nest  (exit code 3) if the code does NOT raise an error of the 
scripterror type. It will print an error message to cerr, too.

Examples:
 {add}     (add ok) failbutnocrash_or_die  -> success
 {1 0 div} failbutnocrash_or_die  -> success
 {add_dd}  failbutnocrash_or_die  -> quit

Author: Diesmann

FirstVersion: 090209

Availability: SLI-2.0

SeeAlso: unittest::fail_or_die, assert, quit
*/
/failbutnocrash_or_die [/proceduretype]
{
 << exch /func exch >>
  begin

 (echo ")
 /func load pcvs join
 ( exec" | ) join 
 statusdict /argv get First join
 ( -) join /command Set

% command ==

 command 0 shpawn
 pop         % don't know what to do with the boolean returnd by shpawn

 statusdict/exitcodes/scripterror :: eq
 {
  % nest terminated with a SLI error, e.g. StackUnderflow
  % this is the desired behavior
 }
 {
  % nest chrashed, for example with return values 
  %  statusdict/exitcodes/abort ::    (e.g. caused by failed assertion)
  %  statusdict/exitcodes/segfault :: (e.g illegal access of memory)
  % or nest completed successfully
  % this is the undesired behavior
   M_FATAL (unittest::failbutnocrash_or_die)
   (Code block failed to raise an error)
   /die_message lookup { (: ) exch join join } if
   (.\nCode block:\n) join /func load pcvs join  message

  M_FATAL (unittest::failbutnocrash_or_die) (Exiting with code 3.) message

  3 quit_i  

 }
 ifelse

  end
} def

/failbutnocrash_or_die[/proceduretype /stringtype]
{
  << >> 
  begin
    /die_message Set
    failbutnocrash_or_die
  end
} bind def


/* BeginDocumentation
Name: unittest::crash_or_die - Execute a code block and exit with exit code 3 if nest does not crash

Synopsis: { code } [string] crash_or_die -> -

Description:

The procedure tests that the code block crashes nest. An example
is a call of operator add_dd in a situation where the operand stack is empty. 
add_dd does not check whether the operand stack contains enough data, no exception
is raised. Therefore, the assertion in the C++ implementation of add_dd that the
operand stack contains at least 2 elements fails and nest terminates with the exit
code statusdict::exitcodes::abort. This is the desired behavior and therefore 
crash_or_die reports success. There are several alternative and undesired behaviors 
of the code block in question. add_dd may raise a StackUnderflow error, return without 
raising an error, or crash because of a segmentation fault. This is not the expected behavior 
and therefore crash_or_die reports a problem. Like failbutnocrash_or_die, crash_or_die 
survives a crash of the code block by probing the code block in a separate nest instance 
and inspecting the result. The procedure uses the symbolic exit codes defined in 
statusdict::exitcodes, abort is one of them.
This test does not work if nest is compiled with the NDEBUG flag set and it is not
safe to just test for a crash because nest may become inconsistent without crashing. 
Therefore, crah_or_die always reports success if the NDEBUG flag is set.

Diagnostics:
This function will quit nest (exit code 3) if the code does NOT crash nest
with exit code abort. It will print an error message to cerr, too.

Examples:
 {add_dd} (add_dd)  crash_or_die  -> success
 {add}      crash_or_die  -> quit


Author: Diesmann

FirstVersion: 090209

Availability: SLI-2.0

SeeAlso: unittest::failbutnocrash_or_die, assert, quit
*/
/crash_or_die [/proceduretype]
{
 << exch /func exch >>
 begin
 statusdict/ndebug :: not
 {

  (echo ")
  /func load pcvs join
  ( exec" | ) join 
  statusdict /argv get First join
  ( -) join /command Set

  command 0 shpawn
  pop         % don't know what to do with the boolean returnd by shpawn

  statusdict/exitcodes/abort :: eq
  {
   % nest terminated with abort, usually a failed assertion
   % this is the desired behavior
  }
  {
   % nest chrashed, for example with return values 
   %  statusdict/exitcodes/scripterror ::    (a SLI exception)
   %  statusdict/exitcodes/segfault :: (e.g illegal access of memory)
   % or nest completed successfully
   % this is the undesired behavior
   M_FATAL (unittest::crash_or_die)
   (Code block failed to crash)
   /die_message lookup { (: ) exch join join } if
   (.\nCode block:\n) join /func load pcvs join  message

   M_FATAL (unittest::crash_or_die) (Exiting with code 3.) message

   3 quit_i  
  }
  ifelse
 } if   % of ndebug
 end
} def

/crash_or_die[/proceduretype /stringtype]
{
  << >> 
  begin
    /die_message Set
    crash_or_die
  end
} bind def



/* BeginDocumentation
Name: unittest::ToUnitTestPrecision  - reduce argument to specified precision.
   
Synopsis:
   double integer ToUnitTestPrecision -> double or integer
   array  integer ToUnitTestPrecision -> array
   other  integer ToUnitTestPrecision -> other
   
Description:
Reduces its double argument to the precision specified by the integer.
If the first argument is an array, ToUnitTestPrecision is recursively
applied to all elements of the array. Any other first argument is
returned unchanged. This is useful in processing heterogeneous arrays.
ToUnitTestPrecision uses a C++ output stream to carry out the reduction
of precision and  manipulator setprecision() is used to set the precision.
The contents of the stream is converted back to a numerical object by
operator token. This guarantees that for the conversion from text to double
the same algorithm
is used for the argument of  ToUnitTestPrecision and the reference data,
usually explicitly specified in the test file as an array. 
The double argument may contain an integral value or be converted to and
integral value due to the required precision. In this case the output
operator of the stream may decide to represent the value as an integer,
i. e. without a decimal point, trailing zeros, or an exponent. This often
increases the readability of reference data arrays (see e.g. test_iaf). The
SLI interpreter function then returns an object of type integer.
The operators token and cvd use the same algorithm for converting text to
to a numerical value. However cvd always return a double.
ToUnitTestPrecision is not an efficient algorithm for rounding numerical
values. The idea rather is to have an algorithm which is guaranteed to be
compatible with the text stream output of a simulation.


Examples:
    7.83635342928 6 ToUnitTestPrecision  -->    7.83635
  -32.38763534    6 ToUnitTestPrecision  -->  -32.3876
        (flower)  6 ToUnitTestPrecision  -->   (flower)
  [7.83635342928 (flower) -32.38763534]
                  6 ToUnitTestPrecision   --> [7.83635 (flower) -32.3876]
  -70             6 ToUnitTestPrecision type --> /integertype
  -70.0           6 ToUnitTestPrecision type --> /integertype
  
Author: Markus Diesmann

FirstVersion: 071108

SeeAlso: unittest::assert_or_die, token, cvd, testsuite::test_iaf
*/

/ToUnitTestPrecision [/doubletype /integertype]
{
 osstream pop exch setprecision exch <- str token rolld pop pop
} def

/ToUnitTestPrecision [/arraytype /integertype]
{
 exch {1 index ToUnitTestPrecision} Map exch pop
} def

/ToUnitTestPrecision [/anytype /integertype]
{
 pop % do nothing
} def




/* BeginDocumentation
Name: unittest::InflateUnitTestData  - reformat compressed reference data
Synopsis:
 array1 array2 InflateUnitTestData -> array3
Parameters:
 array1 - array of sorted simulation step sizes [h1,...,hn]
          All simulation step sizes must be multiples of the
	  smallest one h1.
 array2 - array of reference data with one row per data point.
          the last element v in each row is the value of the recorded
	  quantity. The first element in each row is the time stamp
	  t1 of the smallest step size. The time stamps of larger
	  step sizes are located between t1 and v if the temporal
	  position coincides with t1.
 array3 - array of length n with one entry per step size h1,...,hn.
          Each entry is a two-dimensional vector of tuples [ti,vi],
	  where ti is a time stamp and vi the corresponding value of
	  the recorded quantity.
Description:
 The idea of the compressed data format is to represent the reference
 data in a compact human readable format at the end of the testsuite
 files. Although redundant, the time stamps of all step sizes
 corresponding to a particular reference data point are listed. 
Examples:
% h=   (in ms)
[ 0.1   0.2    0.5   1.0 ]
%
% time                    voltage
[
[  1                     -69.4229]    
[  2     1               -68.8515]    
[  3                     -68.2858]    
[  4     2               -67.7258]
[  5           1         -67.1713]
[  6     3               -66.6223]
[  7                     -66.0788]
[  8     4               -65.5407]
[  9                     -65.008]
[ 10     5     2    1    -64.4806]
[ 11                     -63.9584]
]
 

 
Author: Markus Diesmann
FirstVersion: 071128
SeeAlso: unittest::ToUnitTestPrecision
*/

/InflateUnitTestData
{
 <</di [] >> begin
 /d Set /h Set

 h 1 Drop h [1] Part div {round cvi} Map /hm Set

 d [/All [1 -1]] Part /di AppendTo

 hm
 {
  /m Set          % multipler of highest resolution

  d [[1 -1] 1] Part           % first and last entry at highest resolution
  {cvd m div ceil cvi m mul} 
    1 MapAt                   % reduce to first entry available at this resolution
  d [1 1] Part sub 1 add      % index in data array
  m append /r Set             % distance between available data points 


  d r Take [/All [2 -1]] Part /di AppendTo

  d {[2] Drop } r Range 1 1 Partition MapAt /d Set
 }
 forall

 [h di] Transpose
 end
} def




/* BeginDocumentation
Name: unittest::mpirun_self - calls a distributed version of nest on the calling file

Synopsis: integer mpirun_self -> -

Description:

The command starts a distributed version of nest with the file
containing the call of mpirun_self.  The argument of mpirun_self is
the number of jobs that should participate in the distributed
computation,

Examples:


Author: Diesmann

FirstVersion: 090716

SeeAlso: unittest::distributed_assert_or_die, nest_indirect
*/
/mpirun_self
{
 statusdict/files :: First  mpirun shpawn exch pop  % keep only istream
}
def

/* 
Name: :exec_test_function - Execute test function in distributed job

Synopsis: array func exec_test_function -> -

Description:
The array argument is ignored. The test function func is executed and the 
result sent back to the calling process via stdout (shpawn piping). The test
function must leave exactly one element on the stack.
*/
/:exec_test_function
{
  exch pop          % don't need the list of number of jobs
  mark exch exec    % execute the test function
                    % if the test function terminates with an exception,
                    % execution will terminate here, and no result
                    % will be sent below    
  counttomark /stackload Set
  stackload 1 eq 
  {
    % handle case with one result special, since we need result from stack    
    /result :send_distributed
  }
  {
    stackload 0 eq 
      { /passed :send_distributed } 
      { /error  :send_distributed }  % more than one item left on stack
    ifelse
  } ifelse
  % remove all remaining return values including the mark
  counttomark npop pop
}
def

/* 
Name: :send_distributed - Package and send message from parallel run

Synposis: [result] flag :send_distributed -> - 

Description:

This function packages builds a message from a parallel process and dispatches it
via pipe to the controlling process.

flag   - one of /passed, /result and /error
result - any data (no nested dicts), will be used only if flag is /result

The message dispatched to the controlling process is a string with the format

   :FLAG:RANK:[RESULT AS STRING]:

where FLAG is (passed), (result) or (error), RANK is the MPI rank of
the executing process and RESULT AS STRING is present only for the
/result flag an is the result of calling pcvs on the result argument
to send_distributed.  This message is suitable for collection by
:collect_distributed_results.

Note: result can be a dict, but must not contain dicts

SeeAlso: unittest:mpirun_self
*/
/:send_distributed
{
  /flag Set
  (:) flag cvs join (:) join Rank cvs join (:) join
  flag /result eq 
  { 
    % if result is dict, serialize as array 
    exch    
    DictQ { cva /:serialized:dict exch 2 arraystore } if    
    pcvs join 
  } if
  (:) join
  =
} def

% compile input line regexp once while installing unittest library
% used by :parse_input_line
/:message_line_regexp (:(passed|result|error):([0-9]+):(.*):) regcomp def

% string [begin end] :extract_regexp_match -> string[begin:end]  (pythonic indexing)
/:extract_regexp_match
{
  arrayload 2 eq assert 
  1 pick sub  % stack: string start length
  getinterval
} def

/* 
 Name: :parse_message_line - Parse one input line for message and return flag and result

 Synposis: string :parse_message_line -> [flag rank [result]] true
                                       false

 Description:
 Parses string to see if it is a message from :send_distributed. Returns false if
 no match, otherwise, returns an array containing the messag flag (/passed, /result, /error),
 the rank of the sending process (as int) and, for flag /result, the result as string.
 */
/:parse_message_line
{
  << >> begin
    /line Set

    mark  % so we can clear any mess left in case of errors

    % The argument value 4 is for regexec per r9713. Once #560 is fixed, it 
    % may need to be changed.
    :message_line_regexp line 4 0 regexec

    % The remaining analysis is done in a stopped context, so we avoid the need for
    % deeply nested if-elses.
    {
      0 neq { pop stop } if  % regexp did not match, remove meaningless match array

      /matches Set  % array of matches: [total regexp, flag, rank, result]

      % ensure that regexp matched entire line
      matches 0 get arrayload % get start and end of total match
      2 eq (unittest:::parse_message_line: clean message line) assert_or_die  
      line length eq exch 0 eq and not { stop }  if  

      % extract flag and check it is valid
      /flag line matches 1 get :extract_regexp_match cvlit def
      [/passed /result /error] flag MemberQ not { stop } if

      % extract rank and check it is valid
      /rank line matches 2 get :extract_regexp_match cvi def
      rank 0 lt { stop } if

      % extract result, must be empty unless flag==/result
      /result line matches 3 get :extract_regexp_match def
      result length 0 gt flag /result neq and { stop } if

      % We now know that
      %   - flag is valid
      %   - rank is non-negative int
      %   - if flag is not /result, then result is empty
      % i.e., the message is correctly formatted

      % convert result to SLI objects; empty array for empty string
      result cst % convert to token string
      empty not  % test array for emptyness, not string, to handle strings of spaces
      { cvx_a exec } if   % otherwise, leave empty array 
      /result Set
      % if result is serialized dict, de-serialize
      result ArrayQ 
      { 
        empty not
        {
	  First /:serialized:dict eq 
	  { 
            result length 2 eq 
            (unittest:::parse_message_line: deserialization structure ok) assert_or_die
	    result 1 get dup length 2 mod 0 eq 
            (unittest:::parse_message_line: proper key-value pairs) assert_or_die
	    2 Partition   % split array to key-value pairs
	    /result << >> def         % insert into result dict   
	    { result exch arrayload ; put } forall	
	  } if
	}
	{
	  pop
	} ifelse
      }
      {
        pop
      } ifelse     

      % We leave the output array on the stack. We do NOT push a bool to
      % signal status, that is indicated by the fact that we did not call stop
      [ flag rank result ]
    } 
    stopped
    {
      % stopped, either explicitly above or due to error; 
      % we clean up if error and signal that we did not parse a message
      errordict /newerror get { errordict /newerror false put } if
      counttomark npop pop
      false
    }
    {
      % not stopped, we got proper result, flag true
      counttomark 1 eq assert  % only result error on stack
      exch pop                 % remove mark      
      true      
    } ifelse
} def 


/* 
Name: :collect_distributed_results - Collects results from spawned parallel runs

Synopsis: istream num_procs mode :collect_distributed_results -> array status

Description:

The input argument to :collect_distributed_results are the istream
returned by mpirun_self and the number of mpi processes started.
:collect_distributed_results reads from this istream the output of all
ranks in the parallel process executed by mpirun_self. The individual
processes should send results by calling :send_distributed.

If mode is 
  /none no MPI process should send a result 
  /one  precisely one MPI process should send a result
  /each each MPI process should send a result
In any case, all MPI process should pass.

The array returned contains one string result sent, in arbitrary order. 
status is true if the requirements according to mode are fulfilled. status
may be false if one or several parallel processes have crashed.

SeeAlso: unittest::mpirun_self
*/
/:collect_distributed_results
{  
  << >> begin  
  /mode Set
  /nproc Set
  nproc array /pass_count   Set  % array with one element per rank  
  nproc array /result_count Set  % array with one element per rank  
  [ exch
    { 
      eof 
      {
	exit
      } 
      {
	getline
	{
          :parse_message_line
	  { 
            arrayload 3 eq 
            (unittest:::collect_distributed_results: good parse) assert_or_die
	    
	    /res Set /rank Set /flag Set
            [/passed /result] flag MemberQ   % executed correctly, count as pass
            {
	      % hike result counter for rank	    
	      /pass_count pass_count dup rank get 1 add rank exch put def
	    } if
            /result flag eq   % got result
            {
	      % hike result counter for rank	    
	      /result_count result_count dup rank get 1 add rank exch put def
	      res exch % leave result on stack	    
	    } if
	  } if
	} if 
      } ifelse 
    } loop pop
  ]

  % pass count must be exactly one for all ranks
  /ones_array nproc array 1 add def
  pass_count ones_array eq 

  % test whether mode requirements are fulfilled
  mark
    mode /each eq { result_count ones_array eq exit } case
    mode /one  eq { result_count Plus 1 eq exit     } case
    mode /none eq { result_count Plus 0 eq exit     } case
  switch
  and

  mode /none eq { exch pop } if  % drop empty result array

  end  

} def


/* BeginDocumentation
Name: unittest::distributed_assert_or_die - Checks whether code is independent of number of number of jobs

Synopsis: array proc proc distributed_assert_or_die -> -
          array proc distributed_assert_or_die -> -

Description:

The array specifies a list of numbers of
jobs. distributed_assert_or_die executes the procedure specified as
the second argument in parallel for all of the numbers of jobs given
in the arrays. This means that distributed_assert_or_die carries out
as many distributed simulations as there are entries in the array. 

In each of the simulations a single job is expected to return a
value on the stack. It is of no importance which job returns the
value.  After completion of all simulations distributed_assert_or_die
compares the results of all the simulations using the second proc
as the test function. If no second proc is given distributed_assert_or_die
requires that all results are "true".

A variant of this function  distributed_invariant_assert_or_die
requires all results to be identical.

distributed_assert_or_die assumes that it is called from a file and
that this file contains only a single call of
distributed_assert_or_die because all distributed instances of the
simulator will rerun this file.  In addition distributed_assert_or_die
assumes that the test file is run with a version of nest capable of
spawning further instances of nest.  This can be assured by using
nest_indirect instead of the nest binary directly. The test file
should not make the call of distributed_assert_or_die or the
termination of the test file depending on the status flags is_mpi or
have_mpi. This would interfere with the proper functioning of
distributed_assert_or_die and the status flags have already been
checked by nest_indirect at this point.

Author: Diesmann

FirstVersion: 090715

SeeAlso: unittest::distributed_invariant_assert_or_die, nest_indirect, unittest::mpirun_self, unittest::assert_or_die
*/
/distributed_assert_or_die 
[/arraytype /proceduretype /proceduretype]
{
 rollu    % safe the test function
 statusdict/is_mpi ::   % are we in a distributed job ?
 {
   % we are in a distributed job    
   :exec_test_function    
 }
 {
   % we are in the wrapper dispatching and collecting
   % we need to launch the distributed jobs
   pop                 % don't need the test function
   {
     /num_procs Set
     (N_MPI: ) =only num_procs =   % display number of processes
 
     num_procs mpirun_self
     num_procs /one :collect_distributed_results
     % got a result from one MPI process
     (unittest::distributed_assert_or_die: collected results) assert_or_die          
    } Map
   1 Flatten              % unpack, so we have flat list with one item per process     
   exch 
   (unittest::distributed_assert_or_die: results ok) assert_or_die  % using the test function
 }
 ifelse 
}
def

/distributed_assert_or_die 
[/arraytype /proceduretype]
{
 { true exch { and } Fold } distributed_assert_or_die  % all runs return true 
}
def 


/* BeginDocumentation
Name: unittest::distributed_invariant_assert_or_die - Checks whether code is independent of number of number of jobs

Synopsis: array proc distributed_invariant_assert_or_die -> -
       
Description:

The array specifies a list of numbers of
jobs. distributed_assert_or_die executes the procedure specified as
the second argument in parallel for all of the numbers of jobs given
in the arrays. This means that distributed_invariant_assert_or_die carries out
as many distributed simulations as there are entries in the array. 

In each of the simulations a single job is expected to return a
value on the stack. It is of no importance which job returns the
value.  After completion of all simulations distributed_invariant_assert_or_die
checks whether the results of all runs are identical.

See distributed_assert_or_die for further documentation and 
implementation details.

Author: Diesmann

FirstVersion: 100925

SeeAlso: unittest::distributed_assert_or_die, nest_indirect, unittest::mpirun_self, unittest::assert_or_die
*/
/distributed_invariant_assert_or_die
[/arraytype /proceduretype]
{
 {Split length 1 eq} distributed_assert_or_die    % all runs return same result
}
def 


/* BeginDocumentation
Name: unittest::distributed_collect_assert_or_die - Checks whether result is independent of number of number of jobs

Synopsis: array proc proc distributed_collect assert_or_die -> -
          array proc      distributed_collect assert_or_die -> -

Description:

The array specifies a list of numbers of
jobs. distributed_collect_assert_or_die executes the procedure specified as
the second argument in parallel for all of the numbers of jobs given
in the arrays. This means that distributed_collect_assert_or_die carries out
as many distributed simulations as there are entries in the array. 

In each of the simulations all jobs are expected to return a value on
the stack. The order in which the jobs are completed is of no
importance.  After completion of all simulations
distributed_collect_assert_or_die compares the sets of results of all
the simulations using the comparison function supplied by the second 
procedure. If the second procedure is not given it requires that all
jobs of all runs return true.

A variant of this function distributed_collect_assert_or_die
requires that the contents of all sets of results is identical. The
order of the results in each set is irrelevant.

distributed_collect_assert_or_die assumes that it is called from a
file and that this file contains only a single call of
distributed_collect_assert_or_die because all distributed instances of
the simulator will rerun this file.  In addition the function assumes
that the test file is run with a version of nest capable of spawning
further instances of nest.  This can be assured by using nest_indirect
instead of the nest binary directly. The test file should not make the
call of distributed_collect_assert_or_die or the termination of the
test file depending on the status flags is_mpi or have_mpi. This would
interfere with the proper control flow and the status flags have
already been checked by nest_indirect at this point.

Author: Diesmann

FirstVersion: 100925

SeeAlso: unittest::distributed_assert_or_die, unittest::distributed_collect_assert_or_die, nest_indirect, unittest::mpirun_self, unittest::assert_or_die
*/
/distributed_collect_assert_or_die 
[/arraytype /proceduretype /proceduretype]
{
  rollu  % save the test function
  statusdict/is_mpi ::   
  {
    % we are in a distributed job    
    :exec_test_function    
  }
  {
    % we are in the wrapper dispatching and collecting
    % we need to launch the distributed jobs
    pop                 % don't need the test function
    {
      /num_procs Set
      (N_MPI: ) =only num_procs =   % display number of processes

      num_procs mpirun_self
      num_procs /each :collect_distributed_results
      (unittest::distributed_collect_assert_or_die: collected results)
      assert_or_die          % got a result from each MPI process
    } Map
    exch
    (unittest::distributed_collect_assert_or_die: results ok) assert_or_die  % using the test function
  }
  ifelse 
}
def


/distributed_collect_assert_or_die 
[/arraytype /proceduretype]
{
 { {true exch {and} Fold}  Map  true exch {and} Fold } 

 distributed_collect_assert_or_die   % all runs return true for all jobs 
}
def 

/* BeginDocumentation
Name: unittest::distributed_process_invariant_collect_assert_or_die - Checks whether the pooled results of all ranks are equal, independent of the number of MPI processes

Synopsis: array proc distributed_process_invariant_collect_assert_or_die -> -
      
Description:

The array specifies a list of numbers of
jobs. distributed_process_invariant_collect_assert_or_die executes the
procedure specified as the second argument in parallel for all of the
numbers of jobs given in the arrays. This means that
distributed_process_invariant_collect_assert_or_die carries out as many
distributed simulations as there are entries in the array.

In each of the simulations all jobs are expected to return a value on
the stack. The order in which the jobs are completed is of no
importance.  After completion of all simulations
distributed_process_invariant_ collect_assert_or_die requires that the
contents of all sets of results, pooled across ranks, is
identical. The order of the results in each set is irrelevant.

See distributed_collect_assert_or_die for further documentation and
implementation details and distributed_rank_invariant_collect_assert_or_die for 
a version requiring identical output from each rank.

Author: Diesmann

FirstVersion: 100925

SeeAlso: unittest::distributed_assert_or_die, unittest::distributed_collect_assert_or_die, nest_indirect, unittest::mpirun_self, unittest::assert_or_die
*/
/distributed_process_invariant_collect_assert_or_die
[/arraytype /proceduretype]
{
  
  {   
    { 1 Flatten } Map   % combine results across ranks for each process
    /results Set

    % build reference from results of one process;
    % create dictionary with individual results as keys and number as values
    /reference << >> def
    results First 
    {
      pcvs cvlit /key Set
      reference key known
      {
        reference dup key get 1 add   % add one
        key exch put	
      }
      {
        reference key 1 put           % add new key with value one
      } ifelse
    } forall

    % now check for identical counts in results from all other processes
    results Rest
    {
      /counts reference clonedict exch pop def
      {
        pcvs cvlit /key Set
        counts key known
        {
	  counts dup key get 1 sub   % subtract one
	  key exch put	
	}
        {
          counts /error 99 put       % flag error
          exit
	}
        ifelse
      } forall      
      counts /error known
      { 
        false   % something went wrong
      }
      {
        true counts values { 0 eq and } Fold  % ensure all counts are zero
      }
      ifelse
    } Map    
    % ensure all processes gave true
    true exch { and } Fold
  }    
  distributed_collect_assert_or_die  % all runs return same collected result
}
def 


/* BeginDocumentation
Name: unittest::distributed_process_invariant_events_assert_or_die - Checks whether the pooled event dictionaries from all ranks are equal, independent of the number of MPI processes

Synopsis: array proc distributed_process_invariant_events_assert_or_die -> -
      
Description:

The array specifies a list of numbers of
jobs. distributed_process_invariant_events_assert_or_die executes the
procedure specified as the second argument in parallel for all of the
numbers of jobs given in the arrays. This means that
distributed_process_invariant_events_assert_or_die carries out as many
distributed simulations as there are entries in the array.

In each of the simulations all jobs are expected to return a single
events dictionary (from a recording device) on the stack.
the stack. The order in which the jobs are completed is of no
importance.  After completion of all simulations
distributed_process_invariant_events_assert_or_die requires that the
pooled contents of the events dictionaries is invariant under the
number of MPI processes.

See distributed_collect_assert_or_die for further documentation and
implementation details and distributed_rank_invariant_collect_assert_or_die for 
a version requiring identical output from each rank.

Author: Plesser

FirstVersion: 2012-05-22

SeeAlso: unittest::distributed_assert_or_die, unittest::distributed_collect_assert_or_die, nest_indirect, unittest::mpirun_self, unittest::assert_or_die
*/
/distributed_process_invariant_events_assert_or_die
[/arraytype /proceduretype]
{
  {  % evaluation function; called with array of arrays of event dicts on stack  
    /results Set

    % ensure that all dicts have identical keys
    results Flatten
    dup First keys dup /keylits Set
    /nkeys keylits length def
    { cvs } Map Sort () exch { join } Fold /refkeys Set % string of sorted keys
    true exch Rest { keys { cvs } Map Sort () exch { join } Fold refkeys eq and } Fold
    (unittest::distributed_process_invariant_assert_or_die: consistent keys) assert_or_die

    % For each run, go through events dicts and convert to arrays of strings, with one string per event,
    % combining data from all keys for that event (fixed order of keys), then combine across
    % ranks for each run and sort
    results { [] exch { /d Set [ keylits { d exch get } forall ] 
                               { nkeys arraystore () exch { cvs (_) join join } Fold } MapThread join
                      } Fold Sort 
            } Map

    % we now have array of arrays, should be identical
    dup First /reference Set
    Rest true exch { reference eq and } Fold
  }    
  distributed_collect_assert_or_die  % all runs return same collected result
}
def 


/* BeginDocumentation
Name: unittest::distributed_rank_invariant_collect_assert_or_die - Checks whether all ranks produce equal results, independent of the number of MPI processes

Synopsis: array proc distributed_rank_invariant_collect_assert_or_die -> -
      
Description:

The array specifies a list of numbers of
jobs. distributed_rank_invariant_collect_assert_or_die executes the
procedure specified as the second argument in parallel for all of the
numbers of jobs given in the arrays. This means that
distributed_rank_invariant_collect_assert_or_die carries out as many
distributed simulations as there are entries in the array.

In each of the simulations all jobs are expected to return a value on
the stack. The order in which the jobs are completed is of no
importance.  After completion of all simulations
distributed_rank_invariant_collect_assert_or_die requires that the
contents of the results of all ranks are identical, independent of
the number of processes.

See distributed_collect_assert_or_die for further documentation and
implementation details and distributed_process_invariant_collect_assert_or_die for 
a version requiring identical output from each rank.

Author: Diesmann

FirstVersion: 100925

SeeAlso: unittest::distributed_assert_or_die, unittest::distributed_collect_assert_or_die, nest_indirect, unittest::mpirun_self, unittest::assert_or_die
*/
/distributed_rank_invariant_collect_assert_or_die
[/arraytype /proceduretype]
{
  { % evaluation function
    1 Flatten                 % flat array of results from each rank, irrespective of process
    dup First /reference Set  % take first as reference
    Rest                      % results from all other ranks
    true exch { reference eq and } Fold     % check if all equal reference
  } distributed_collect_assert_or_die  
}
def 


/* BeginDocumentation
Name: unittest::distributed_pass_or_die - Checks whether code runs for different numbers of jobs

Synopsis: array proc distributed_pass_or_die -> -

Description:
proc is executed for the different numbers of jobs specified in the
array. distributed_pass_or_die only checks whether proc completes
without errors and does not leave anything on the stack.

Author: Diesmann

FirstVersion: 100918

SeeAlso:  unittest::distributed_assert_or_die, nest_indirect, unittest::mpirun_self, unittest::assert_or_die
*/

/distributed_pass_or_die
{
  statusdict/is_mpi ::   % are we in a distributed job ?
  {
    :exec_test_function    
  }
  {
    % we are in the wrapper dispatching and collecting
    % we need to launch the distributed jobs
    pop                 % don't need the test function
    {
      /num_procs Set
      (N_MPI: ) =only num_procs =   % display number of processes
      
      num_procs mpirun_self
      num_procs /none :collect_distributed_results
      (unittest::distributed_pass_or_die: collected results) assert_or_die % got a result from each MPI process
    } forall
    % if we get here, all assert_or_dies passed above, so all is fine
  }
  ifelse 
}
def

end