Skip to content

Commit 04f07c8

Browse files
authored
Update testing workflow (#57)
* Rename "update" workflow to "run_tests" * Remove steps pushing badges to gh-badges branch Not necessary * Only test on pushed to main and PRs to main * Run tests for earliest and latest matlab releases that are available * Fix indent error * Update run_tests.yml * Update run_tests.yml * Update Collection.m Use containers.Map if dictionarry is unavailable for compatibility * Update Collection.m * Fix bugs in Collection after introducing use of fallback to containers.Map if dictionary is unavailable * Add metatype registry. Singleton class for storing cached metatype objects for the openminds type classes * Create badge for latest release only * Update run_tests.yml Fix yaml error * Update run_tests.yml
1 parent 70b6839 commit 04f07c8

11 files changed

Lines changed: 490 additions & 78 deletions

File tree

Lines changed: 20 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
# This way tests will run on any push to a feature branch and when feature
66
# branches are merged to main, but not repeated when PRs are actually merged.
77
push:
8-
branches-ignore: ["pipeline", "gh-badges"]
8+
branches: [ "main" ]
99
paths-ignore:
1010
- '*README.md'
1111
- '*md'
@@ -18,9 +18,14 @@ on:
1818
jobs:
1919
# This workflow contains a single job called "test"
2020
test:
21-
name: Test openMINDS_MATLAB
22-
# The type of runner that the job will run on
21+
name: Test openMINDS_MATLAB (${{ matrix.MATLABVersion }})
2322
runs-on: ubuntu-latest
23+
strategy:
24+
fail-fast: false
25+
matrix:
26+
MATLABVersion: [R2021b, R2024b]
27+
env:
28+
LatestMATLABVersion: R2024b
2429

2530
# Steps represent a sequence of tasks that will be executed as part of the job
2631
steps:
@@ -30,31 +35,36 @@ jobs:
3035

3136
- name: Set up MATLAB
3237
uses: matlab-actions/setup-matlab@v2
38+
with:
39+
release: ${{ matrix.MATLABVersion }}
3340

3441
# Check for MATLAB code issues in the project.
3542
- name: Check for MATLAB code issues
43+
if: matrix.MATLABVersion == env.LatestMATLABVersion
3644
uses: matlab-actions/run-command@v2
37-
if: always()
3845
with:
3946
command: addpath(genpath("tools")),codecheckToolbox()
4047

4148
# Upload code issues report
4249
- name: Upload SARIF file
50+
if: matrix.MATLABVersion == env.LatestMATLABVersion
4351
uses: github/codeql-action/upload-sarif@v3
4452
with:
4553
# Path to SARIF file relative to the root of the repository
4654
sarif_file: docs/reports/code_issues.sarif
4755

4856
# Run all tests in the project.
4957
- name: Run tests
50-
uses: matlab-actions/run-command@v2
5158
if: always()
59+
uses: matlab-actions/run-command@v2
5260
with:
53-
command: addpath(genpath("tools")), testToolbox()
61+
command: |
62+
doCreateBadge = "${{ matrix.MATLABVersion }}" == "${{ env.LatestMATLABVersion }}";
63+
addpath(genpath("tools")), testToolbox("CreateBadge", doCreateBadge)
5464
5565
# Commit updated SVG badges for the issues and tests (if changed)
5666
- name: Commit svg badges if updated
57-
if: always()
67+
if: matrix.MATLABVersion == env.LatestMATLABVersion
5868
continue-on-error: true
5969
run: |
6070
git config user.name "${{ github.workflow }} by ${{ github.actor }}"
@@ -70,8 +80,8 @@ jobs:
7080
fi
7181
7282
- name: Upload code coverage report to Codecov (https://app.codecov.io/gh/openMetadataInitiative/openMINDS_MATLAB)
83+
if: matrix.MATLABVersion == env.LatestMATLABVersion
7384
uses: codecov/codecov-action@v4
74-
if: always()
7585
with:
7686
token: ${{ secrets.CODECOV_TOKEN }}
7787
files: docs/reports/codecoverage.xml
@@ -82,36 +92,12 @@ jobs:
8292
if: always()
8393
with:
8494
files: "docs/reports/test-results.xml"
95+
check_name: "Test Results (${{ matrix.MATLABVersion }})"
8596

8697
# Save the contents of the reports directory as an artifact
8798
- name: Save reports directory
8899
uses: actions/upload-artifact@v4
89100
if: always()
90101
with:
91-
name: reports
102+
name: reports-${{ matrix.MATLABVersion }}
92103
path: docs/reports
93-
94-
- name: Checkout gh-badges branch
95-
uses: actions/checkout@v4
96-
with:
97-
ref: gh-badges
98-
path: gh-badges
99-
token: ${{ secrets.GITHUB_TOKEN }}
100-
101-
- name: Push to gh-badges
102-
run: |
103-
cp .github/badges/code_issues.svg gh-badges/.github/badges/code_issues.svg
104-
cp .github/badges/tests.svg gh-badges/.github/badges/tests.svg
105-
cd gh-badges
106-
107-
git config user.name "${{ github.workflow }} by ${{ github.actor }}"
108-
git config user.email "<>"
109-
110-
# Only proceed with commit and push if changes are detected
111-
if [[ $(git add .github/badges/* --dry-run | wc -l) -gt 0 ]]; then
112-
git add .github/badges/*
113-
git commit -m "Update code issues and tests badges"
114-
git push -f
115-
else
116-
echo "Nothing to commit"
117-
fi

code/+openminds/@Collection/Collection.m

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,22 @@
4747
% Description of the metadata collection
4848
Description (1,1) string
4949
end
50+
51+
properties (Dependent, Access = private)
52+
NumNodes
53+
NumTypes
54+
end
5055

5156
properties (SetAccess = protected)
5257
% Nodes - Dictionary storing instances as values with identifiers
5358
% as keys
54-
Nodes (1,1) dictionary
59+
Nodes {mustBeA(Nodes, ["dictionary", "containers.Map"])} = containers.Map %#ok<MCHDP>
5560
end
5661

5762
properties (SetAccess = protected, Hidden)
5863
% TypeMap - Keeps a map/dictionary of types and instance ids to
5964
% efficiently extract instances of a specific type.
60-
TypeMap (1,1) dictionary
65+
TypeMap {mustBeA(TypeMap, ["dictionary", "containers.Map"])} = containers.Map %#ok<MCHDP>
6166
end
6267

6368
properties
@@ -105,8 +110,14 @@
105110
end
106111

107112
% Initialize protected maps
108-
obj.Nodes = dictionary;
109-
obj.TypeMap = dictionary;
113+
if exist("isMATLABReleaseOlderThan", "file") ...
114+
&& not(isMATLABReleaseOlderThan("R2022b"))
115+
obj.Nodes = dictionary;
116+
obj.TypeMap = dictionary;
117+
else % Backwards compatibility
118+
obj.Nodes = containers.Map;
119+
obj.TypeMap = containers.Map;
120+
end
110121

111122
obj.initializeFromInstances(instance)
112123

@@ -115,15 +126,33 @@
115126
end
116127
end
117128

129+
methods
130+
function numNodes = get.NumNodes(obj)
131+
if isa(obj.Nodes, 'dictionary')
132+
numNodes = numEntries(obj.Nodes);
133+
elseif isa(obj.Nodes, 'containers.Map')
134+
numNodes = length(obj.Nodes);
135+
end
136+
end
137+
138+
function numTypes = get.NumTypes(obj)
139+
if isa(obj.TypeMap, 'dictionary')
140+
numTypes = numEntries(obj.TypeMap);
141+
elseif isa(obj.TypeMap, 'containers.Map')
142+
numTypes = length(obj.TypeMap);
143+
end
144+
end
145+
end
146+
118147
methods
119148
function len = length(obj)
120-
len = numEntries(obj.Nodes);
149+
len = obj.NumNodes;
121150
end
122151

123152
function tf = isKey(obj, identifier)
124153
% isKey - Check if collection has a node with the given key / identifier
125154
tf = false;
126-
if isConfigured(obj.Nodes)
155+
if obj.NumNodes > 0
127156
if isKey(obj.Nodes, identifier)
128157
tf = true;
129158
end
@@ -161,7 +190,7 @@ function add(obj, instance, options)
161190
% Todo:work for arrays
162191
tf = false;
163192

164-
if isConfigured(obj.Nodes)
193+
if obj.NumNodes > 0
165194
if isKey(obj.Nodes, instance.id)
166195
tf = true;
167196
end
@@ -179,14 +208,18 @@ function remove(obj, instance)
179208
error('Unexpected type "%s" for instance argument', class(instance))
180209
end
181210

182-
if isConfigured(obj.Nodes) && isKey(obj.Nodes, instanceId)
211+
if obj.NumNodes > 0 && isKey(obj.Nodes, instanceId)
183212
try
184213
instanceType = class( obj.Nodes{instanceId} );
185214
catch % < R2023a
186215
instance = obj.Nodes(instanceId);
187216
instanceType = class( instance{1} );
188217
end
189-
obj.Nodes(instanceId) = [];
218+
if isa(obj.Nodes, "dictionary")
219+
obj.Nodes(instanceId) = [];
220+
else
221+
obj.Nodes.remove(instanceId);
222+
end
190223

191224
allIds = obj.TypeMap(instanceType);
192225
obj.TypeMap(instanceType) = { setdiff( allIds{1}, instanceId ) };
@@ -196,11 +229,11 @@ function remove(obj, instance)
196229
end
197230

198231
function instance = get(obj, nodeKey)
199-
if isMATLABReleaseOlderThan("R2023b")
232+
if exist("isMATLABReleaseOlderThan", "file") && not( isMATLABReleaseOlderThan("R2023b") )
233+
instance = obj.Nodes{nodeKey};
234+
else
200235
instance = obj.Nodes(nodeKey);
201236
instance = instance{1};
202-
else
203-
instance = obj.Nodes{nodeKey};
204237
end
205238
end
206239

@@ -212,7 +245,7 @@ function remove(obj, instance)
212245

213246
tf = false;
214247

215-
if ~isConfigured(obj.Nodes)
248+
if obj.NumNodes == 0
216249
return
217250
end
218251

@@ -232,14 +265,23 @@ function remove(obj, instance)
232265

233266
instances = [];
234267

235-
if ~isConfigured(obj.Nodes)
268+
if obj.NumNodes == 0
236269
return
237270
end
238271

239272
instanceKeys = obj.getInstanceKeysForType(type);
240273
if isempty(instanceKeys); return; end
241274

242-
instances = obj.Nodes(instanceKeys);
275+
if isa(obj.Nodes, 'dictionary')
276+
instances = obj.Nodes(instanceKeys);
277+
else
278+
instances = cell(1, numel(instanceKeys));
279+
for i = 1:numel(instanceKeys)
280+
instances{i} = obj.Nodes(instanceKeys{i});
281+
end
282+
instances = [instances{:}];
283+
end
284+
243285
instances = [instances{:}]; % Create non-cell array
244286

245287
% Filter by property values:
@@ -255,7 +297,12 @@ function remove(obj, instance)
255297
end
256298

257299
function updateLinks(obj)
258-
for instance = obj.Nodes.values
300+
allInstances = obj.Nodes.values;
301+
if isa(obj.Nodes, 'containers.Map')
302+
allInstances = [allInstances{:}];
303+
end
304+
305+
for instance = allInstances
259306
obj.addNode(instance{1}, ...
260307
'AddSubNodesOnly', true, ...
261308
'AbortIfNodeExists', false);
@@ -380,7 +427,7 @@ function load(obj, filePath)%, options)
380427
return
381428
end
382429

383-
if isConfigured(obj.Nodes)
430+
if obj.NumNodes > 0
384431
if isKey(obj.Nodes, instance.id)
385432
% warning('Node with id %s already exists in collection', instance.id)
386433
if options.AbortIfNodeExists
@@ -395,7 +442,7 @@ function load(obj, filePath)%, options)
395442

396443
% Add to TypeMap: Todo: Separate method
397444
instanceType = class(instance);
398-
if isConfigured(obj.TypeMap) && isKey(obj.TypeMap, instanceType)
445+
if obj.NumTypes > 0 && isKey(obj.TypeMap, instanceType)
399446
if isMATLABReleaseOlderThan("R2023b")
400447
existingInstances = obj.TypeMap(instanceType);
401448
obj.TypeMap(instanceType) = {[existingInstances{:}, string(instance.id)]};
@@ -472,15 +519,20 @@ function initializeFromInstances(obj, instance)
472519
function instanceKeys = getInstanceKeysForType(obj, instanceType)
473520
% getInstanceKeysForType Get all ids for instances of a given type
474521

475-
if isConfigured(obj.TypeMap)
522+
if obj.NumTypes > 0
476523
typeKeys = obj.TypeMap.keys;
477524

478525
isMatch = strcmp(typeKeys, instanceType.ClassName);
479526
if any(isMatch)
480-
if isMATLABReleaseOlderThan("R2023b")
481-
instanceKeys = string( obj.TypeMap(typeKeys(isMatch)) );
482-
else
483-
instanceKeys = obj.TypeMap{typeKeys(isMatch)};
527+
if isa(obj.TypeMap, 'dictionary')
528+
if isMATLABReleaseOlderThan("R2023b")
529+
instanceKeys = string( obj.TypeMap(typeKeys(isMatch)) );
530+
else
531+
instanceKeys = obj.TypeMap{typeKeys(isMatch)};
532+
end
533+
elseif isa(obj.TypeMap, 'containers.Map')
534+
instanceKeys = obj.TypeMap(typeKeys{isMatch});
535+
instanceKeys = instanceKeys{1};
484536
end
485537
else
486538
instanceKeys = {};

code/+openminds/@Collection/loadInstances.m

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -83,21 +83,11 @@ function resolveLinks(instance, instanceIds, instanceCollection)
8383
return
8484
end
8585

86-
persistent schemaInspectorMap
87-
if isempty(schemaInspectorMap)
88-
schemaInspectorMap = dictionary;
89-
end
90-
91-
instanceType = class(instance);
92-
if ~isConfigured(schemaInspectorMap) || ~isKey(schemaInspectorMap, instanceType)
93-
schemaInspectorMap(instanceType) = openminds.internal.SchemaInspector(instance);
94-
end
95-
96-
schemaInspector = schemaInspectorMap(instanceType);
86+
metaType = openminds.internal.meta.fromInstance(instance);
9787

98-
for i = 1:schemaInspector.NumProperties
99-
thisPropertyName = schemaInspector.PropertyNames{i};
100-
if schemaInspector.isPropertyWithLinkedType(thisPropertyName)
88+
for i = 1:metaType.NumProperties
89+
thisPropertyName = metaType.PropertyNames{i};
90+
if metaType.isPropertyWithLinkedType(thisPropertyName)
10191
linkedInstances = instance.(thisPropertyName);
10292

10393
resolvedInstances = cell(size(linkedInstances));
@@ -137,7 +127,7 @@ function resolveLinks(instance, instanceIds, instanceCollection)
137127
instance.(thisPropertyName) = resolvedInstances;
138128
end
139129

140-
elseif schemaInspector.isPropertyWithEmbeddedType(thisPropertyName)
130+
elseif metaType.isPropertyWithEmbeddedType(thisPropertyName)
141131
embeddedInstances = instance.(thisPropertyName);
142132

143133
for j = 1:numel(embeddedInstances)

code/internal/+openminds/+abstract/Schema.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
end
2828

2929
properties (Abstract, Constant, Hidden)
30-
LINKED_PROPERTIES struct %todo: name and classname, not openminds uri
30+
LINKED_PROPERTIES struct
3131
EMBEDDED_PROPERTIES struct
3232
end
3333

0 commit comments

Comments
 (0)