Skip to content

Commit b285920

Browse files
Fix namespace bug for hdmf-experimental types (#779)
* Fix namespace bug for hdmf-experimental types Adds a workaround for a bug where neurodata types are incorrectly assigned the 'hdmf-experimental' namespace instead of 'hdmf-common'. If the class does not exist for 'hdmf-experimental' but does for 'hdmf-common', the namespace and typename are corrected accordingly. See hdmf-dev/hdmf#1347 for details. * Update getNeurodataTypeInfo.m * Added unittest --------- Co-authored-by: Ben Dichter <[email protected]>
1 parent 95bf510 commit b285920

File tree

2 files changed

+170
-2
lines changed

2 files changed

+170
-2
lines changed

+io/getNeurodataTypeInfo.m

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,34 @@
4646

4747
% Get full classname given a namespace and a neurodata type
4848
if hasTypeDef && hasNamespace
49-
typeInfo.typename = matnwb.common.composeFullClassName(...
50-
typeInfo.namespace, typeInfo.name);
49+
typeInfo.typename = char( matnwb.common.composeFullClassName(...
50+
typeInfo.namespace, typeInfo.name) );
51+
52+
if strcmp(typeInfo.namespace, 'hdmf-experimental') && ~exist(typeInfo.typename, 'class')
53+
typeInfo = correctNamespaceIfShouldBeHdmfCommon(typeInfo);
54+
end
55+
end
56+
end
57+
58+
function typeInfo = correctNamespaceIfShouldBeHdmfCommon(typeInfo)
59+
% correctNamespaceIfShouldBeHdmfCommon - Correct namespace if value in file is wrong.
60+
%
61+
% This function provides a workaround for a bug where the namespace of a
62+
% neurodata type was wrongly written to file as hdmf-experimental instead
63+
% of hdmf-common.
64+
%
65+
% If the namespace of a type is hdmf-experimental, and the corresponding type
66+
% class does not exist in MATLAB, but the equivalent hdmf_common class exists,
67+
% the namespace is changed from hdmf-experimental to hdmf-common.
68+
%
69+
% The bug is described in this issue:
70+
% https://github.com/hdmf-dev/hdmf/issues/1347
71+
72+
if strcmp(typeInfo.namespace, 'hdmf-experimental') && ~exist(typeInfo.typename, 'class')
73+
correctedTypename = replace(typeInfo.typename, 'hdmf_experimental', 'hdmf_common');
74+
if exist(correctedTypename, 'class') == 8
75+
typeInfo.typename = correctedTypename;
76+
typeInfo.namespace = 'hdmf-common';
77+
end
5178
end
5279
end
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
classdef GetNeurodataTypeInfoTest < matlab.unittest.TestCase
2+
% GetNeurodataTypeInfoTest - Unit tests for io.getNeurodataTypeInfo function.
3+
4+
properties (TestParameter)
5+
HDMFCommonType = {'DynamicTable', 'VectorData', 'VectorIndex'}
6+
end
7+
8+
methods (Test)
9+
function testEmptyInput(testCase)
10+
% Test that empty input returns empty typeInfo
11+
typeInfo = io.getNeurodataTypeInfo([]);
12+
13+
testCase.verifyEqual(typeInfo.namespace, '');
14+
testCase.verifyEqual(typeInfo.name, '');
15+
testCase.verifyEqual(typeInfo.typename, '');
16+
end
17+
18+
function testValidNeurodataType(testCase)
19+
% Test with valid neurodata type and namespace
20+
attributeInfo = struct(...
21+
'Name', {'neurodata_type', 'namespace'}, ...
22+
'Value', {'DynamicTable', 'hdmf-common'});
23+
24+
typeInfo = io.getNeurodataTypeInfo(attributeInfo);
25+
26+
testCase.verifyEqual(typeInfo.namespace, 'hdmf-common');
27+
testCase.verifyEqual(typeInfo.name, 'DynamicTable');
28+
testCase.verifyEqual(typeInfo.typename, 'types.hdmf_common.DynamicTable');
29+
end
30+
31+
function testOnlyNeurodataType(testCase)
32+
% Test with only neurodata_type attribute (no namespace)
33+
attributeInfo = struct(...
34+
'Name', {'neurodata_type'}, ...
35+
'Value', {'DynamicTable'});
36+
37+
typeInfo = io.getNeurodataTypeInfo(attributeInfo);
38+
39+
testCase.verifyEqual(typeInfo.namespace, '');
40+
testCase.verifyEqual(typeInfo.name, 'DynamicTable');
41+
testCase.verifyEqual(typeInfo.typename, '');
42+
end
43+
44+
function testOnlyNamespace(testCase)
45+
% Test with only namespace attribute (no neurodata_type)
46+
attributeInfo = struct(...
47+
'Name', {'namespace'}, ...
48+
'Value', {'hdmf-common'});
49+
50+
typeInfo = io.getNeurodataTypeInfo(attributeInfo);
51+
52+
testCase.verifyEqual(typeInfo.namespace, 'hdmf-common');
53+
testCase.verifyEqual(typeInfo.name, '');
54+
testCase.verifyEqual(typeInfo.typename, '');
55+
end
56+
57+
function testHdmfExperimentalFallbackToHdmfCommon(testCase, HDMFCommonType)
58+
% Test fallback correction for VectorData with incorrect
59+
% hdmf-experimental namespace (should be hdmf-common)
60+
attributeInfo = struct(...
61+
'Name', {'neurodata_type', 'namespace'}, ...
62+
'Value', {HDMFCommonType, 'hdmf-experimental'});
63+
64+
typeInfo = io.getNeurodataTypeInfo(attributeInfo);
65+
66+
% Should be corrected to hdmf-common
67+
testCase.verifyEqual(typeInfo.namespace, 'hdmf-common');
68+
testCase.verifyEqual(typeInfo.name, HDMFCommonType);
69+
testCase.verifyEqual(typeInfo.typename, sprintf('types.hdmf_common.%s', HDMFCommonType));
70+
end
71+
72+
function testCellStringValueHandling(testCase)
73+
% Test that cell string values are handled correctly
74+
attributeInfo = struct(...
75+
'Name', {'neurodata_type', 'namespace'}, ...
76+
'Value', {{'DynamicTable'}, {'hdmf-common'}});
77+
78+
typeInfo = io.getNeurodataTypeInfo(attributeInfo);
79+
80+
testCase.verifyEqual(typeInfo.namespace, 'hdmf-common');
81+
testCase.verifyEqual(typeInfo.name, 'DynamicTable');
82+
testCase.verifyEqual(typeInfo.typename, 'types.hdmf_common.DynamicTable');
83+
end
84+
85+
function testNoFallbackForValidHdmfExperimentalType(testCase)
86+
% Test that valid hdmf-experimental types are not incorrectly
87+
% changed to hdmf-common
88+
%
89+
% Note: This test verifies that if a type genuinely belongs to
90+
% hdmf-experimental and exists there, it should not be changed.
91+
% EnumData is an example type that exists only in
92+
% hdmf-experimental.
93+
94+
% First, check if types.hdmf_experimental exists and has types
95+
if exist('types.hdmf_experimental.EnumData', 'class') == 8
96+
attributeInfo = struct(...
97+
'Name', {'neurodata_type', 'namespace'}, ...
98+
'Value', {'EnumData', 'hdmf-experimental'});
99+
100+
typeInfo = io.getNeurodataTypeInfo(attributeInfo);
101+
102+
% Should remain hdmf-experimental since the class exists there
103+
testCase.verifyEqual(typeInfo.namespace, 'hdmf-experimental');
104+
testCase.verifyEqual(typeInfo.typename, 'types.hdmf_experimental.EnumData');
105+
else
106+
% If hdmf-experimental types don't exist, skip this test
107+
testCase.assumeTrue(false, ...
108+
'Skipping test: types.hdmf_experimental.EnumData class not found');
109+
end
110+
end
111+
112+
function testNoFallbackForNonExistentType(testCase)
113+
% Test that non-existent types in hdmf-experimental that also
114+
% don't exist in hdmf-common remain unchanged (the typename
115+
% will still point to hdmf-experimental since no fallback exists)
116+
attributeInfo = struct(...
117+
'Name', {'neurodata_type', 'namespace'}, ...
118+
'Value', {'NonExistentType', 'hdmf-experimental'});
119+
120+
typeInfo = io.getNeurodataTypeInfo(attributeInfo);
121+
122+
% Should remain hdmf-experimental since there's no hdmf-common fallback
123+
testCase.verifyEqual(typeInfo.namespace, 'hdmf-experimental');
124+
testCase.verifyEqual(typeInfo.name, 'NonExistentType');
125+
testCase.verifyEqual(typeInfo.typename, 'types.hdmf_experimental.NonExistentType');
126+
end
127+
128+
function testCoreNamespaceUnchanged(testCase)
129+
% Test that core namespace types are not affected by fallback logic
130+
attributeInfo = struct(...
131+
'Name', {'neurodata_type', 'namespace'}, ...
132+
'Value', {'NWBFile', 'core'});
133+
134+
typeInfo = io.getNeurodataTypeInfo(attributeInfo);
135+
136+
testCase.verifyEqual(typeInfo.namespace, 'core');
137+
testCase.verifyEqual(typeInfo.name, 'NWBFile');
138+
testCase.verifyEqual(typeInfo.typename, 'types.core.NWBFile');
139+
end
140+
end
141+
end

0 commit comments

Comments
 (0)