Skip to content

Commit

Permalink
Add function for running nwbinspector on tutorial files during testing
Browse files Browse the repository at this point in the history
  • Loading branch information
ehennestad committed Nov 3, 2024
1 parent 95b5e5e commit dea1d2e
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 22 deletions.
111 changes: 90 additions & 21 deletions +tests/+unit/TutorialTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,7 @@ function setupClass(testCase)
setenv('PYTHONPATH', fileparts(mfilename('fullpath')));

nwbClearGenerated()
end
end

methods (TestClassTeardown)
function tearDownClass(testCase) %#ok<MANU>
%generateCore()
testCase.addTeardown(@generateCore)
end
end

Expand All @@ -79,60 +74,134 @@ function setupMethod(testCase)

methods (Test)
function testTutorial(testCase, tutorialFile) %#ok<INUSD>
% Intentionally capturing output, in order for tests to cover
% code which overloads display methods for nwb types/objects.
C = evalc( 'run(tutorialFile)' ); %#ok<NASGU>
testCase.testReadTutorialNwbFileWithPynwb()

testCase.readTutorialNwbFileWithPynwb()
%testCase.inspectTutorialFileWithNwbInspector()
end
end

methods
function testReadTutorialNwbFileWithPynwb(testCase)
function readTutorialNwbFileWithPynwb(testCase)

% Retrieve all files generated by tutorial
nwbListing = dir('*.nwb');

for i = 1:numel(nwbListing)
nwbFilename = nwbListing(i).name;
if any(strcmp(nwbFilename, tests.unit.TutorialTest.SkippedFiles))
continue
end

nwbFileNameList = testCase.listNwbFiles();
for nwbFilename = nwbFileNameList
try
try
io = py.pynwb.NWBHDF5IO(nwbListing(i).name);
io = py.pynwb.NWBHDF5IO(nwbFilename);
nwbObject = io.read();
testCase.verifyNotEmpty(nwbObject, 'The NWB file should not be empty.');
io.close()

catch ME
if strcmp(ME.identifier, 'MATLAB:undefinedVarOrClass') && ...
contains(ME.message, 'py.pynwb.NWBHDF5IO')

pythonExecutable = tests.util.getPythonPath();
cmd = sprintf('"%s" -B -m read_nwbfile_with_pynwb %s',...
pythonExecutable, nwbFilename);

status = system(cmd);

if status ~= 0
error('Failed to read NWB file "%s" using pynwb', nwbFilename)
end
else
rethrow(ME)
end
end

catch ME
error(ME.message)
%testCase.verifyFail(sprintf('Failed to read file %s with error: %s', nwbListing(i).name, ME.message));
end
end
end

function inspectTutorialFileWithNwbInspector(testCase)
% Retrieve all files generated by tutorial
nwbFileNameList = testCase.listNwbFiles();
for nwbFilename = nwbFileNameList
results = py.list(py.nwbinspector.inspect_nwbfile(nwbfile_path=nwbFilename));

if isempty(cell(results))
return
end

results = testCase.convertNwbInspectorResultsToStruct(results);
T = struct2table(results); disp(T)

for j = 1:numel(results)
testCase.verifyLessThanOrEqual(results(j).importance, 0, ...
sprintf('Message: %s\nLocation: %s\n File: %s\n', ...
string(results(j).message), results(j).location, results(j).filepath))
end
end
end
end

methods (Access = private)
function nwbFileNames = listNwbFiles(testCase)
nwbListing = dir('*.nwb');
nwbFileNames = string( {nwbListing.name} );
nwbFileNames = setdiff(nwbFileNames, testCase.SkippedFiles);
assert(isrow(nwbFileNames), 'Expected output to be a row vector')
if ~isscalar(nwbFileNames)
if iscolumn(nwbFileNames)
nwbFileNames = transpose(nwbFileNames);
end
end
end
end

methods (Static)
function resultsOut = convertNwbInspectorResultsToStruct(resultsIn)
CHECK_IGNORE = "check_image_series_external_file_valid";

C = cell(resultsIn);

resultsOut = struct(...
'importance', {}, ...
'severity', {}, ...
'location', {}, ...
'filepath', {}, ...
'check_name', {}, ...
'ignore', {});

for i = 1:numel(C)
resultsOut(i).importance = double( py.getattr(C{i}.importance, 'value') );
resultsOut(i).severity = double( py.getattr(C{i}.severity, 'value') );

try
resultsOut(i).location = string(C{i}.location);
catch
resultsOut(i).location = "N/A";
end

resultsOut(i).message = string(C{i}.message);
resultsOut(i).filepath = string(C{i}.file_path);
resultsOut(i).check_name = string(C{i}.check_function_name);
resultsOut(i).ignore = any(strcmp(CHECK_IGNORE, resultsOut(i).check_name));

% Special case to ignore
if resultsOut(i).location == "/acquisition/ExternalVideos" && ...
resultsOut(i).check_name == "check_timestamps_match_first_dimension"
resultsOut(i).ignore = true;
end
end
resultsOut([resultsOut.ignore]) = [];
end
end
end

function tutorialNames = listTutorialFiles()
% listTutorialFiles - List names of all tutorial files (exclude skipped files)
rootPath = getMatNwbRootDirectory();
L = dir(fullfile(rootPath, 'tutorials'));
L = cat(1, ...
dir(fullfile(rootPath, 'tutorials', '*.mlx')), ...
dir(fullfile(rootPath, 'tutorials', '*.m')) ...
);

L( [L.isdir] ) = []; % Ignore folders
tutorialNames = setdiff({L.name}, tests.unit.TutorialTest.SkippedTutorials);
end
Expand Down
3 changes: 2 additions & 1 deletion +tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pynwb
hdf5plugin
hdf5plugin
nwbinspector
1 change: 1 addition & 0 deletions nwbtest.m
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
ws = pwd;

nwbClearGenerated(); % Clear default files if any.
cleanupObj = onCleanup(@() generateCore);
cleaner = onCleanup(@generateCore); % Regenerate core when finished

pvcell = struct2pvcell(parser.Unmatched);
Expand Down

0 comments on commit dea1d2e

Please sign in to comment.