Related Documentation:
Here we describe how to create a test for the Heccer package. Since Heccer and it’s associated simulation objects are all compiled into a library, programs are written in C and compiled against this library to test each use case. In this example, we create suitable tests for the PulseGen{}Simulation Object (simobj_PulseGen{}) described in Create Solver Object.
The source files for the Heccer tests are found in the tests/code directory within the Heccer Component.
Since we are testing a new simulation object, the test framework must be made ‘aware’ of it. A line must be added to each of the files:
tests/code/main.c
tests/code/main.h |
In tests/code/main.c we add:
struct simobj_PulseGen *ppg = NULL;
|
Then in tests/code/main.h we add:
extern struct simobj_PulseGen *ppg;
|
The main driver for the test framework will now be aware of the PulseGen{} simulation object. Next a source file is created that demonstrates a specific use case.
Ideally, output should be tested against a use case that can be verified to work correctly. Since the PulseGen{} object is a port of the same object used by GENESIS 2, we can obtain output from a GENESIS 2 script to compare with the output of our simulation object.
To test PulseGen{} we want a very basic script that will test minimal functionality in each of its trigger modes. Here is an example that creates three PulseGen{} objects, one for each trigger mode, and sends outputs to a file:
create pulsegen /pulse0
setfield ˆ level1 50.0 width1 3.0 delay1 5.0 level2 -20.0 width2 5.0 delay2 8.0 baselevel 10.0 trig_mode 0 create pulsegen /pulse1 setfield ˆ level1 50.0 width1 3.0 delay1 5.0 level2 -20.0 width2 5.0 delay2 8.0 baselevel 10.0 trig_mode 1 create pulsegen /pulse2 setfield ˆ level1 50.0 width1 3.0 delay1 5.0 level2 -20.0 width2 5.0 delay2 8.0 baselevel 10.0 trig_mode 2 create asc_file /pulse0_out setfield /pulse0_out filename "pulse-freerun.txt" flush 1 leave_open 1 append 1 float_format \%0.9g addmsg /pulse0 /pulse0_out SAVE output call /pulse0_out OUT_OPEN create asc_file /pulse1_out setfield /pulse1_out filename "pulse-exttrig.txt" flush 1 leave_open 1 append 1 float_format \%0.9g addmsg /pulse1 /pulse1_out SAVE output call /pulse1_out OUT_OPEN create asc_file /pulse2_out setfield /pulse2_out filename "pulse-extgate.txt" flush 1 leave_open 1 append 1 float_format \%0.9g addmsg /pulse2 /pulse2_out SAVE output call /pulse2_out OUT_OPEN reset setclock 0 0.5 step 200 |
From data output files we can see what the behavior of the object is supposed to be like.
When we run our corresponding PulseGen{} tests we match the value from the solved variable (pdVms0) for each time step and see that it matches the output we obtain from the GENESIS 2 script. We therefore know this use case works and can set the expected output for the test specification to the output of our program. This serves as a good base case for testing. If these tests should happen to fail then we know some necessary functionality has been compromised during the development of new use cases.
A test program for Heccer consists of a set of defines, structures, and a main function. Simply changing sections of an existing program is enough to get the test running. The code in each test has inline comments to help understand the functionality, but we’ll go through it to be thorough.
At the top of the file you will see defines for setting some simulation and output parameters. Here are some available options:
So here are the settings for our test:
#define HECCER_TEST_REPORTING_GRANULARITY 1
#define HECCER_TEST_STEPS 200 #define HECCER_TEST_TESTED_THINGS ( HECCER_DUMP_VM_COMPARTMENT_MATRIX | HECCER_DUMP_VM_MECHANISM_OPERATIONS ) #define HECCER_TEST_TIME_STEP (0.5) |
The test will run for 200 steps with a time step of 0.5 seconds. It will produce output, complete with Heccer internal data, for every single step of the simulation.
Next, some declarations for data structures that the test will operate on are shown.
We now declare a Compartment{} struct. As the V m variable (membrane potential) is addressed to produce output, the PulseGen{} object needs the Compartment{} declaration even although it does not perform any compartment specific operations:
struct Compartment compSoma =
{ //m administrative overhead { //m type of structure MATH_TYPE_Compartment, }, //m index of parent compartment, -1 for none -1, //m descriptive values, alphabetical order /* double dCm; */ 4.57537e-11, // unscaled 0.0164, /* double dEm; */ -0.08, /* double dInitVm; */ -0.068, /* double dInject; */ 0, /* double dRa; */ 360502, // unscaled 2.5, /* double dRm; */ 3.58441e+08, // unscaled 1 }; |
These declarations set this Compartment{} to be a Math type with no parent, Cm to 4.57537e-11, E m to -0.08, initial membrane potential to -0.068, inject to 0, Ra to 360502, and Rm to 3.58441e+08, respectively.
Then an intermediary is declared. Here, the default is used as the test will only use one compartment with one intermediary:
int piC2m[] =
{ 0, -1, }; struct Intermediary inter = { //m compartment array 1, &compSoma, //m all other mathematical components NULL, //m compartment 2 first mechanism number piC2m, }; |
Next, the main function that actually uses the functions in the simulation object is given. Here a PulseGen{} object is created and its parameters set. This is followed by two #define s, one to connect the PulseGen{} object to a compartment variable, the other to perform a simulation update time step.
Allocating a new PulseGen{} is done via:
ppg = PulseGenNew("pulsegen freerun");
|
The parameters of PulseGen{} are then set with the PulseGenSetFields() function:
double dLevel1 = 50.0;
double dWidth1 = 3.0; double dDelay1 = 5.0; double dLevel2 = -20.0; double dWidth2 = 5.0; double dDelay2 = 8.0; double dBaseLevel = 10.0; int iTriggerMode = FREE_RUN; PulseGenSetFields(ppg, dLevel1, dWidth2, dDelay1, dLevel2, dWidth2, dDelay2, dBaseLevel, iTriggerMode); |
Next, for the define HECCER_TEST_INITIATE we fetch a compartment variable from Heccer and attach it to the PulseGen{} output:
#define HECCER_TEST_INITIATE \
double *pdVm = HeccerAddressCompartmentVariable(pheccer, 0, "Vm"); \ PulseGenAddVariable(ppg, pdVm) |
Finally, for HECCER_TEST_SCHEDULE we add the PulseGen() step function so that it is performed on every step of the simulation:
#define HECCER_TEST_SCHEDULE PulseGenSingleStep(ppg, (dSimulationTime))
|
The tests/code directory has a Makefile.am which automake uses to generate a Makefile for building each test program. We only need to add some information to a couple of places and automake will handle the rest.
In the target check_PROGRAMS we need to add the resulting executable for our test program. For the case pulsegen-freerun.c we add:
pulsegen-freerun \
|
Next we add rules near the bottom of the file to build and link our program. It is preferable to keep this in alphabetical order with the rest of the test programs build rules:
pulsegen_freerun_DEPENDENCIES = ../../libheccer.a main.c
pulsegen_freerun_LDADD = -L../.. -lheccer -lm |
This must be done for each test we want to add.
Note: As defined in the first target, the resulting executable is called pulsegen-freerun. However, to declare the accompanying build rule you must use an underscore, e.g. pulsegen_freerun.
The final step is to declare a test specification which tests the new test program against output that is known to be correct.
Create the file tests/specifications/pulsegen.t and input the following to test the pulsegen-freerun test program. This assumes that the expected output is in the tests/specifications/strings directory with the filename pulsegen-freerun.txt.
#!/usr/bin/perl -w
# use strict; my $test = { command_definitions => [ { arguments => [ ], command => ’tests/code/pulsegen-freerun’, command_tests => [ { description => "Can a single pulsegen object output amplitude in free run mode ?", read => (join ’’, ‘cat $::config->{core_directory}/tests/specifications/strings/pulsegen-freerun.txt‘), }, ], description => "pulsegen functionality, can we output a current in free run mode?", }, ], description => "pulsegen simulation object", name => ’pulsegen.t’, }; return $test; |
For more information on declaring test specifications see Neurospaces Tester.