Skip to content

Commit

Permalink
Improve test coverage (#589)
Browse files Browse the repository at this point in the history
* Improve coverage for nwbRead/nwbExport

* Remove tutorialTest (function-based)

* Create TutorialTest (class-based unit test)

* Add test classes (issue #583 )

* Update description for PynwbTutorialTest

* Add specific tests for io.timestamp2datetime

* Update unit tests

* Create getTutorialNwbFilePath.m

Add function to create a filepath for saving tutorial nwb files in a fixed folder

* Fix: Add starting_time when creating TimeSeries objects (#584)

Also, use misc.getTutorialNwbFilePath to create a filepath for saving tutorial nwb file

* Update several tutorials: Specify timezone or NWBFile's session_start_time property

* Fix bug in io.timestamp2datetime (Issue #585)

Some dates are assigned wrongly when Day, Month and Year are set individually, as the datetime object internally/silently adjusts month if day is not valid

* Update timestamp2datetime.m

Fix: Should not preallocate datetimeArray as it is not known beforehand whether the timestamp has a timezone or not (datetime.empty contrains to datetime w/o timezone)

* Update GenerationTest.m

Ensure test can be run from any current working directory, not just the matnwb repo folder

* Update getMatnwbDir.m

Simplyfy function by using mfilename instead of dbstack

* Delete file which was introduced with issue #585

* Change nwbtest: Add selector options as input; Add cleanup routine

* Add test for constructing Anon with name, value input

* Add specific testing of types.untyped.Set

When writing test, also discovered a but in types.untyped.Set which is fixed

* Remove function call used for debugging

---------

Co-authored-by: Ben Dichter <[email protected]>
  • Loading branch information
ehennestad and bendichter authored Sep 12, 2024
1 parent 9d1caa3 commit fd34d3d
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 25 deletions.
28 changes: 6 additions & 22 deletions +misc/getMatnwbDir.m
Original file line number Diff line number Diff line change
@@ -1,23 +1,7 @@
function [matnwbDir] = getMatnwbDir(varargin)

try
% Get the actual location of the matnwb directory. This assumes "getMatnwbDir" is
% within the +misc matnwb package folder.
fnLoc = dbstack('-completenames');
fnLoc = fnLoc(1).file;
[fnDir,~,~] = fileparts(fnLoc);
[matnwbDir,~,~] = fileparts(fnDir);

if 7 ~= exist(matnwbDir, 'dir')
warning('NWB:GetMatnwbDir:NotFound',...
'Did not find "matnwb" root directory at %s. Using defaults.',...
matnwbDir);
end
catch err
atLine = repmat('@', 1, 7);
fprintf('%s\n%s\n%s\n',...
atLine,...
getReport(err, 'extended', 'hyperlinks', 'on'),...
atLine);
end
function matnwbDir = getMatnwbDir(varargin)
% Get the absolute path for the matnwb directory.

% This assumes "getMatnwbDir" is within the +misc matnwb package folder.
miscFolder = fileparts(mfilename('fullpath'));
matnwbDir = fileparts(miscFolder);
end
6 changes: 5 additions & 1 deletion +tests/+sanity/GenerationTest.m
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
classdef GenerationTest < matlab.unittest.TestCase
properties (MethodSetupParameter)
schemaVersion = setdiff({dir('nwb-schema').name}, {'.', '..'});
schemaVersion = listSchemaVersions()
end

methods (TestClassSetup)
Expand Down Expand Up @@ -62,3 +62,7 @@ function dynamicTableMethodsTest(testCase)
end
end

function schemaVersions = listSchemaVersions()
nwbSchemaDir = fullfile(misc.getMatnwbDir, 'nwb-schema');
schemaVersions = setdiff({dir(nwbSchemaDir).name}, {'.', '..'});
end
89 changes: 89 additions & 0 deletions +tests/+system/NWBFileIOTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,94 @@ function addContainer(testCase, file) %#ok<INUSL>
c = file;
end
end

methods (Test)
function writeMultipleFiles(testCase)

fileA = testCase.file;
fileB = NwbFile( ...
'session_description', 'a second test NWB File', ...
'identifier', 'TEST456', ...
'session_start_time', '2018-12-02T12:57:27.371444-08:00', ...
'file_create_date', '2017-04-15T12:00:00.000000-08:00',...
'timestamps_reference_time', '2018-12-02T12:57:27.371444-08:00');

fileNameA = ['MatNWB.' testCase.className() '.testWriteMultiA.nwb'];
fileNameB = ['MatNWB.' testCase.className() '.testWriteMultiB.nwb'];

nwbExport([fileA, fileB], {fileNameA, fileNameB});
end

function readWithStringArg(testCase)
fileName = ['MatNWB.' testCase.className() '.testReadWithStringArg.nwb'];
fileName = string(fileName);
nwbExport(testCase.file, fileName)
nwbRead(fileName, "ignorecache");
end

function readFileWithoutSpec(testCase)
fileName = ['MatNWB.' testCase.className() '.testReadFileWithoutSpec.nwb'];
nwbExport(testCase.file, fileName)

testCase.deleteGroupFromFile(fileName, 'specifications')
nwbRead(fileName);
end

function readFileWithoutSpecLoc(testCase)
fileName = ['MatNWB.' testCase.className() '.testReadFileWithoutSpecLoc.nwb'];
nwbExport(testCase.file, fileName)

testCase.deleteAttributeFromFile(fileName, '/', '.specloc')

nwbRead(fileName);
end

function readFileWithUnsupportedVersion(testCase)
fileName = ['MatNWB.' testCase.className() '.testReadFileWithUnsupportedVersion.nwb'];
nwbExport(testCase.file, fileName)

testCase.deleteAttributeFromFile(fileName, '/', 'nwb_version')

file_id = H5F.open(fileName, 'H5F_ACC_RDWR', 'H5P_DEFAULT');
io.writeAttribute(file_id, '/nwb_version', '1.0.0')
H5F.close(file_id);

nwbRead(fileName);
end
end

methods (Static, Access = private)
function deleteGroupFromFile(fileName, groupName)
if ~startsWith(groupName, '/')
groupName = ['/', groupName];
end

% Open the HDF5 file in read-write mode
file_id = H5F.open(fileName, 'H5F_ACC_RDWR', 'H5P_DEFAULT');

% Delete the group
H5L.delete(file_id, groupName, 'H5P_DEFAULT');

% Close the HDF5 file
H5F.close(file_id);
end

function deleteAttributeFromFile(fileName, objectName, attributeName)
% Open the HDF5 file in read-write mode
file_id = H5F.open(fileName, 'H5F_ACC_RDWR', 'H5P_DEFAULT');

% Open the object (dataset or group)
object_id = H5O.open(file_id, objectName, 'H5P_DEFAULT');

% Delete the attribute
H5A.delete(object_id, attributeName);

% Close the object
H5O.close(object_id);

% Close the HDF5 file
H5F.close(file_id);
end
end
end

6 changes: 6 additions & 0 deletions +tests/+unit/anonTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,9 @@ function testAnonDataset(testCase)

tests.util.verifyContainerEqual(testCase, nwbRead('testanon.nwb', 'ignorecache'), nwbExpected);
end

function testAnonTypeWithNameValueInput(testCase)
anon = types.untyped.Anon('a', 1);
testCase.verifyEqual(anon.name, 'a')
testCase.verifyEqual(anon.value, 1)
end
5 changes: 5 additions & 0 deletions +tests/+unit/dataPipeTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,11 @@ function testBoundPipe(testCase)
warning('on', debugId);
end

function testConfigurationFromData(testCase)
conf = types.untyped.datapipe.Configuration.fromData(zeros(10,10), 1);
testCase.verifyClass(conf, 'types.untyped.datapipe.Configuration')
end

function data = createData(dataType, size)
data = randi(intmax(dataType), size, dataType);
end
68 changes: 68 additions & 0 deletions +tests/+unit/untypedSetTest.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
function tests = untypedSetTest()
tests = functiontests(localfunctions);
end

function setupOnce(testCase)
rootPath = fullfile(fileparts(mfilename('fullpath')), '..', '..');
testCase.applyFixture(matlab.unittest.fixtures.PathFixture(rootPath));
end

function setup(testCase)
testCase.applyFixture(matlab.unittest.fixtures.WorkingFolderFixture);
end

function testCreateSetWithFunctionInput(testCase)
set = types.untyped.Set(@(key, value) true);
testCase.verifyNotEmpty(set.ValidationFcn)
end

function testCreateSetFromStruct(testCase)
untypedSet = types.untyped.Set( struct('a',1, 'b', 2) );
testCase.verifyEqual(untypedSet.get('a'), 1)
end

function testCreateSetFromNvPairs(testCase)
untypedSet = types.untyped.Set( 'a',1, 'b', 2 );
testCase.verifyEqual(untypedSet.get('a'), 1)
end

function testCreateSetFromNvPairsPlusFunctionHandle(testCase)
untypedSet = types.untyped.Set( 'a',1, 'b', 2, @(key, value) disp('Hello World'));
testCase.verifyEqual(untypedSet.get('a'), 1)
end

function testDisplayEmptyObject(testCase)
emptyUntypedSet = types.untyped.Set();
disp(emptyUntypedSet)
end

function testDisplayScalarObject(testCase)
scalarSet = types.untyped.Set('a',1)
disp(scalarSet)
end

function testGetSetSize(testCase)
untypedSet = types.untyped.Set( 'a',1, 'b', 2 );

[nRowsA, nColsA] = size(untypedSet);

nRowsB = size(untypedSet, 1);
nColsB = size(untypedSet, 2);

testCase.verifyEqual(nRowsA, nRowsB);
testCase.verifyEqual(nColsA, nColsB);
end

function testHorizontalConcatenation(testCase)
untypedSetA = types.untyped.Set( struct('a',1, 'b', 2) );
untypedSetB = types.untyped.Set( struct('c',3, 'd', 3) );

testCase.verifyError(@() [untypedSetA, untypedSetB], 'NWB:Set:Unsupported')
end

function testVerticalConcatenation(testCase)
untypedSetA = types.untyped.Set( struct('a',1, 'b', 2) );
untypedSetB = types.untyped.Set( struct('c',3, 'd', 3) );

testCase.verifyError(@() [untypedSetA; untypedSetB], 'NWB:Set:Unsupported')
end
2 changes: 1 addition & 1 deletion +types/+untyped/Set.m
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
if nargout == 1
varargout{1} = [obj.Count, 1];
else
[varargout{:}] = ones(nargout,1);
varargout = num2cell( ones(1, nargout) );
varargout{1} = obj.Count;
end
end
Expand Down
8 changes: 7 additions & 1 deletion nwbtest.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,19 @@
parser = inputParser;
parser.KeepUnmatched = true;
parser.addParameter('Verbosity', 1);
parser.addParameter('Selector', [])
parser.parse(varargin{:});

ws = pwd;

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

pvcell = struct2pvcell(parser.Unmatched);
suite = TestSuite.fromPackage('tests', 'IncludingSubpackages', true, pvcell{:});
if ~isempty(parser.Results.Selector)
suite = suite.selectIf(parser.Results.Selector);
end

runner = TestRunner.withTextOutput('Verbosity', parser.Results.Verbosity);

Expand Down

0 comments on commit fd34d3d

Please sign in to comment.