The purpose of this page is just to serve as todo or scratch pad for the development project and to list and share some ideas.

After making changes to the code and/or documentation, this page should remain on the website as a reminder of what was done and how it was done. However, there is no guarantee that this page is updated in the end to reflect the final state of the project

So chances are that this page is considerably outdated and irrelevant. The notes here might not reflect the current state of the code, and you should not use this as serious documentation.

Code coverage

Using the untested_functions command for ft_test, we can find which high-level FieldTrip functions are not directly called by any test scripts, i.e., which functions seem to have no coverage at all.

ft_test untested_functions

The result we get by running this command are:

Untested functions:

bis2fieldtrip              
fieldtrip2besa             
fieldtrip2bis              
fieldtrip2spss             
ft_anonymizedata           
ft_audiovideobrowser       
ft_examplefunction         
ft_geometryplot            
ft_multiplotCC             
ft_reproducescript         
ft_sourcemovie             
ft_statistics_analytic     
ft_statistics_crossvalidate
ft_statistics_mvpa         
ft_statistics_stats        
ft_wizard                  
imotions2fieldtrip         
loreta2fieldtrip           
nutmeg2fieldtrip           
spass2fieldtrip            

Number of untested functions: 20

A more complete and detailed code coverage can be obtained with line-by-line coverage of the high-level FieldTrip functions. To do that we first create a test case that includes of the FieldTrip tests, which ensures all of the tests follow the unit testing MATLAB framework.

To generate the report as an HTML file we used the ReportCoverageFor name-value argument of the runtests MATLAB function.

All of that can be done with the code below

Determine code coverage

%%%%%%% Save this to a file "inspect_codecoverage.m" %%%%%%%%%%

function tests = inspect_codecoverage

if nargout
  % This will be executed by RUNTESTS
  tests = functiontests(localfunctions);
else
  error('This is not to be called from the command-line')
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function testEverything(testCase)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Get FieldTrip version and path
[~, ftpath] = ft_version;

% Define the folder containing test scripts
testFolder = fullfile(ftpath, 'test');

% List all m-files in the test folder that start with test_
list = dir(fullfile(testFolder, 'test_*.m'));
% Remove the ".m" extension from test script names
[~, testScripts] = cellfun(@fileparts, {list.name}, 'Uniformoutput', false);

% Some test scripts were causing MATLAB to crash on a DCCN Windows PC, these need to be excluded
indices = ~contains(testScripts, 'test_bug1818');
testScripts = testScripts(indices);
indices = ~contains(testScripts, 'test_old_buffer_latency_bandwidth');
testScripts = testScripts(indices);
indices = ~contains(testScripts, 'test_bug472');
testScripts = testScripts(indices);
success = false(size(testScripts));

% Loop through the test scripts
for i = 1:length(testScripts)
  try
    % Execute the current test script
    fprintf('\nRunning **%s**\n', testScripts{i});
    eval(testScripts{i});
    success(i) = true;
  catch
    success(i) = true;
  end
end

if all(success)
  fprintf('\nAll test scripts ran successfully.\n');
else
  fprintf('\nThe following test scripts failed:\n');
  for i=find(~success)
    fprintf('%s\n', testScripts{i});
  end
end

With the code above saved as a local inspect_codecoverage.m function, we run it using the runtests MATLAB function.

Additionally, we define the functions we want to find the coverage for. Here I opt only for the high-level FieldTrip functions:

%%%%%%% Run this in the command window %%%%%%%%%%

% Get FieldTrip version and path
[~, ftpath] = ft_version;
list = dir(ftpath);

% List all .m files in the FieldTrip path
indices = endsWith({list.name}, {'.m'});
list = list(indices); 

% Exclude "Contents.m"
indices = ~contains({list.name}, {'Contents.m'});
sourceFunctions = list(indices);

% List the high-level FieldTrip functions
sourceFunctions = {sourceFunctions.name};

% Replace "sourceFunctions" with its full filename, including its path
for i = 1:length(sourceFunctions)
  fullname = which(sourceFunctions{i});
  sourceFunctions{i} = fullname;
end

% Run the coverage report
runtests('inspect_codecoverage.m', 'ReportCoverageFor', sourceFunctions);

Using the code above we determined the “full” coverage provided by all the 955 test_xxx scripts. We also determined the “partial” coverage provided by the 202 test_ft_xxx scripts.

It was found that the line-by-line full coverage is 41 %, which can and can be found here. The line-by-line partial coverage is 37 %, which can be found here.

This reveals that the test_ft_xxx scripts already provide most of the current coverage. The remaining test scripts (especially the test_bugXXX and test_issueXXX) were more designed for regression testing and can be continued to be used for that. </div>

A future goal is to also find the coverage of FieldTrip functions that are part of the modules (fileio, preproc, etc.) and other low-level functions.

Alternative way

Another way to find the line-by-line coverage is by adding an instance of the CodeCoveragePlugin class to a test runner. This is part of the class-based unit testing MATLAB framework.

First create the inspect_codecoverage.m function as above and put it inside a new folder codecoverage at the root level. Then run in the command window:

%%%%%%% Run this in the command window %%%%%%%%%%

import matlab.unittest.plugins.CodeCoveragePlugin
import matlab.unittest.plugins.codecoverage.CoverageReport

[~, ftpath] = ft_version;

runner = testrunner('textoutput');
sourceCodeFolder = fullfile(ftpath, '*.m');
reportFolder = 'coverageReport';
reportFormat = CoverageReport(reportFolder);

p = CodeCoveragePlugin.forFolder(sourceCodeFolder,'Producing',reportFormat);
runner.addPlugin(p)

suite = testsuite(fullfile(ftpath, 'codecoverage')); % 'inpect_codecoverage.m' is the only one inside this folder
results = runner.run(suite);

We opted for the first method using runtests and not the alternative with CodeCoveragePlugin, because it has less lines of code, it is easier to interpret for a non-software engineer and provides the same results.

The alternative above does not work; the function inpect_codecoverage.m is not picked up. If you rename it to inspect_test.m or something else with “test” in the filename, it does get picked up.

From the MATLAB documentation: The name of the test file must start or end with the word ‘test’, which is case-insensitive. If the file name does not start or end with the word ‘test’, the tests in the file might be ignored in certain cases.

Parallel execution

All the tests need to run sequentially in one MATLAB session in order to acquire a full coverage report. Hence, at first sight, it seems that the HPC cluster of the DCCN cannot be used to speed up the code coverage process. We can investigate more efficient execution of the coverage tests in the future.

Make compatible test scripts

In FieldTrip we have a lot of test scripts (actually functions) that were written without consideration of the MATLAB testing framework. To make these compatible with runtests, a little bit of code can be added to the top, like this:

function tests = test_whatever

if nargout
  % assume that this is called by RUNTESTS
  tests = functiontests(localfunctions);
else
  % assume that this is called from the command line
  fn = localfunctions;
  for i = 1:numel(fn)
    feval(fn{i});
  end
end % nargout

function executeTest(testCase)
% this subfunction contains the actual code to be tested
% ...