diff --git a/+bids/+internal/format_path.m b/+bids/+internal/format_path.m index b6c339dd..d634dcb2 100644 --- a/+bids/+internal/format_path.m +++ b/+bids/+internal/format_path.m @@ -22,11 +22,11 @@ end if ischar(pth) - pth = strrep(pth, '\', '/'); + pth = strrep(pth, '\', '\\'); elseif iscell(pth) for i = 1:numel(pth) - pth{i} = pathToPrint(pth{i}); + pth{i} = strrep(pth{i}, '\', '\\'); end end diff --git a/+bids/+internal/is_github_ci.m b/+bids/+internal/is_github_ci.m index ce3ff300..3b4ee8e3 100644 --- a/+bids/+internal/is_github_ci.m +++ b/+bids/+internal/is_github_ci.m @@ -4,8 +4,9 @@ is_github = false; GITHUB_WORKSPACE = getenv('HOME'); + IS_CI = getenv('CI'); - if strcmp(GITHUB_WORKSPACE, '/home/runner') + if IS_CI is_github = true; pth = GITHUB_WORKSPACE; diff --git a/+bids/+internal/nansum.m b/+bids/+internal/nansum.m new file mode 100644 index 00000000..bb707cb1 --- /dev/null +++ b/+bids/+internal/nansum.m @@ -0,0 +1,22 @@ +function y = nansum(varargin) + % + % nansum wrapper to deal with missing toolbox or octave + + % (C) Copyright 2023 Remi Gau + + if ~isempty(which('nansum')) + y = nansum(varargin{:}); + return + end + + if bids.internal.is_octave() + tolerant = false; + bids.internal.error_handling(mfilename(), ... + 'notImplemented', ... + 'nansum not implemented', tolerant); + return + end + + y = sum(varargin{:}, 'omitnan'); + +end diff --git a/+bids/+util/create_participants_tsv.m b/+bids/+util/create_participants_tsv.m index 0ee00153..23babd2c 100644 --- a/+bids/+util/create_participants_tsv.m +++ b/+bids/+util/create_participants_tsv.m @@ -55,7 +55,7 @@ if ~isempty(layout.participants) msg = sprintf(['"participant.tsv" already exist for the following dataset. ', ... 'Will not overwrite.\n', ... - '\t%s'], layout.pth); + '\t%s'], bids.internal.format_path(layout.pth)); bids.internal.error_handling(mfilename(), 'participantFileExist', msg, tolerant, verbose); return end diff --git a/+bids/+util/create_scans_tsv.m b/+bids/+util/create_scans_tsv.m index e0e1f2aa..dac60342 100644 --- a/+bids/+util/create_scans_tsv.m +++ b/+bids/+util/create_scans_tsv.m @@ -104,7 +104,9 @@ if exist(fullfile(layout.pth, scans_file), 'file') msg = sprintf(['"scans.tsv" %s already exist for the following dataset.', ... 'Will not overwrite.\n', ... - '\t%s'], scans_file, layout.pth); + '\t%s'], ... + bids.internal.format_path(scans_file), ... + bids.internal.format_path(layout.pth)); bids.internal.error_handling(mfilename(), 'scansFileExist', msg, true, verbose); continue end diff --git a/+bids/+util/tsvread.m b/+bids/+util/tsvread.m index b967512d..7ebd675d 100644 --- a/+bids/+util/tsvread.m +++ b/+bids/+util/tsvread.m @@ -6,7 +6,7 @@ % % file_content = tsvread(filename, field_to_return, hdr) % - % :param filename: filename (can be gzipped) {txt,mat,csv,tsv,json}ename + % :param filename: filename (can be gzipped) {txt,mat,csv,tsv,json} % :type filename: string % % :param field_to_return: name of field to return if data stored in a structure @@ -200,6 +200,15 @@ if S(end) ~= eol S = [S eol]; end + % Byte order mark (BOM),U+FEFF,0xEF,0xBB,0xBF + % matlab case + if S(1) == 65279 + S(1) = []; + end + % octave case + if numel(S) > 2 && isequal(S(1:3), [239 187 191]) + S(1:3) = []; + end S = regexprep(S, {'\r\n', '\r', '(\n)\1+'}, {'\n', '\n', '$1'}); % -Get column names from header line (non-numeric first line) diff --git a/+bids/copy_to_derivative.m b/+bids/copy_to_derivative.m index 64b3f5a8..cc2c80d7 100644 --- a/+bids/copy_to_derivative.m +++ b/+bids/copy_to_derivative.m @@ -316,9 +316,9 @@ function copy_with_symlink(src, target, unzip_files, verbose) if isunix if unzip_files && is_gunzipped(src) - command = sprintf('gunzip --keep --force --stdout %s > %s', src, target(1:end - 3)); + command = sprintf('gunzip -kfc %s > %s', src, target(1:end - 3)); else - command = sprintf('cp --recursive --dereference --force %s %s', src, target); + command = sprintf('cp -rLf %s %s', src, target); end status = system(command); diff --git a/+bids/layout.m b/+bids/layout.m index e1bc8045..56242e26 100644 --- a/+bids/layout.m +++ b/+bids/layout.m @@ -749,7 +749,9 @@ % to avoid excessive warning as sym link are not files if ~exist(dest, 'file') if ~BIDS.is_datalad_ds - msg = ['IntendedFor file ' dest ' from ' file.filename ' not found']; + msg = sprintf('IntendedFor file %s from %s not found', ... + bids.internal.format_path(dest), ... + bids.internal.format_path(file.filename)); bids.internal.error_handling(mfilename, 'IntendedForMissing', msg, tolerant, verbose); end continue diff --git a/.github/workflows/run_tests_matlab.yml b/.github/workflows/run_tests_matlab.yml new file mode 100644 index 00000000..3b9adca5 --- /dev/null +++ b/.github/workflows/run_tests_matlab.yml @@ -0,0 +1,62 @@ +--- +name: 'matlab: tests' + +on: + push: + branches: + - master + - dev + pull_request: + branches: ['*'] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + test: + + + strategy: + fail-fast: false + matrix: + version: [R2021a, R2021b, R2022a, R2022b] + os: [ubuntu-latest, macos-latest, windows-latest] + + runs-on: ${{matrix.os}} + + steps: + - name: Install MATLAB + uses: matlab-actions/setup-matlab@v1.2.4 + with: + release: ${{matrix.version}} + + - name: Clone bids-matlab + uses: actions/checkout@v3 + with: + submodules: true + fetch-depth: 1 + + - name: Install dependencies + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get -y -qq update + sudo apt-get -y install unzip wget + + - name: Install bids validator + run: npm install -g bids-validator + + - name: Install bids example + run: | + cd tests + make data + + - name: Install Moxunit and MOcov + run: | + git clone https://github.com/MOxUnit/MOxUnit.git --depth 1 + git clone https://github.com/MOcov/MOcov.git --depth 1 + + - name: Run commands + uses: matlab-actions/run-command@v1.1.3 + with: + command: run MOxUnit/MOxUnit/moxunit_set_path(); addpath(fullfile(pwd, 'MOcov', 'MOcov')); addpath(getenv('GITHUB_WORKSPACE')); success = run_tests(); + assert(success); diff --git a/.github/workflows/tests.yml b/.github/workflows/run_tests_octave.yml similarity index 66% rename from .github/workflows/tests.yml rename to .github/workflows/run_tests_octave.yml index 73ab42a7..0a823c5d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/run_tests_octave.yml @@ -1,5 +1,5 @@ --- -name: tests +name: 'octave: tests' on: push: @@ -19,16 +19,8 @@ jobs: runs-on: ubuntu-22.04 - strategy: - matrix: - platform: [matlab, octave] - fail-fast: false - steps: - - name: ${{ matrix.platform }} - test - run: echo ${{ matrix.platform }} test - - name: Clone bids-matlab uses: actions/checkout@v3 with: @@ -54,7 +46,6 @@ jobs: git clone https://github.com/MOcov/MOcov.git --depth 1 - name: Install octave - if: matrix.platform == 'octave' run: | sudo apt-get -y -qq update sudo apt-get -y install \ @@ -69,7 +60,6 @@ jobs: make -C MOcov install - name: Install JSONio - if: matrix.platform == 'octave' run: | git clone https://github.com/gllmflndn/JSONio.git --depth 1 cd JSONio @@ -77,19 +67,5 @@ jobs: octave $OCTFLAGS --eval "addpath(fullfile(pwd)); savepath();" - name: Run unit tests Octave - if: matrix.platform == 'octave' run: | octave $OCTFLAGS --eval "success = run_tests(); assert(success);" - - - name: Install MATLAB - if: matrix.platform == 'matlab' - uses: matlab-actions/setup-matlab@v1.2.4 - with: - release: R2020a - - - name: Run MATLAB tests - if: matrix.platform == 'matlab' - uses: matlab-actions/run-command@v1.1.2 - with: - command: run MOxUnit/MOxUnit/moxunit_set_path(); addpath(fullfile(pwd, 'MOcov', 'MOcov')); addpath(getenv('GITHUB_WORKSPACE')); success = run_tests(); - assert(success); diff --git a/run_tests.m b/run_tests.m index 9c00e6e2..17d72972 100644 --- a/run_tests.m +++ b/run_tests.m @@ -9,6 +9,10 @@ with_coverage = true; end + if ispc + with_coverage = false; + end + addpath(fullfile(pwd, 'tests', 'utils')); folderToCover = fullfile(pwd, '+bids'); diff --git a/tests/data/bom_bug_552.tsv b/tests/data/bom_bug_552.tsv new file mode 100644 index 00000000..5dc5f99f --- /dev/null +++ b/tests/data/bom_bug_552.tsv @@ -0,0 +1,91 @@ +onset duration trial_type value sample +61.824 5.0 Control 1 483 +87.296 5.0 Control 1 682 +117.632 5.0 Tapping/Right 3 919 +146.816 5.0 Tapping/Right 3 1147 +181.504 5.0 Control 1 1418 +212.864 5.0 Tapping/Left 2 1663 +240.64 5.0 Tapping/Left 2 1880 +275.712 5.0 Control 1 2154 +311.424 5.0 Tapping/Right 3 2433 +344.832 5.0 Tapping/Right 3 2694 +373.376 5.0 Tapping/Left 2 2917 +404.736 5.0 Tapping/Left 2 3162 +435.712 5.0 Tapping/Left 2 3404 +474.496 5.0 Tapping/Right 3 3707 +512.256 5.0 Tapping/Left 2 4002 +544.64 5.0 Control 1 4255 +570.368 5.0 Tapping/Left 2 4456 +609.28 5.0 Tapping/Right 3 4760 +635.904 5.0 Control 1 4968 +661.504 5.0 Tapping/Left 2 5168 +690.56 5.0 Tapping/Right 3 5395 +720.256 5.0 Tapping/Left 2 5627 +758.144 5.0 Control 1 5923 +784.384 5.0 Control 1 6128 +819.456 5.0 Tapping/Left 2 6402 +847.872 5.0 Control 1 6624 +874.368 5.0 Tapping/Left 2 6831 +900.48 5.0 Control 1 7035 +930.944 5.0 Tapping/Right 3 7273 +965.76 5.0 Tapping/Left 2 7545 +993.792 5.0 Tapping/Right 3 7764 +1021.44 5.0 Control 1 7980 +1046.784 5.0 Tapping/Right 3 8178 +1080.192 5.0 Tapping/Left 2 8439 +1106.56 5.0 Tapping/Right 3 8645 +1144.448 5.0 Tapping/Right 3 8941 +1182.72 5.0 Tapping/Left 2 9240 +1220.48 5.0 Control 1 9535 +1254.784 5.0 Tapping/Right 3 9803 +1281.408 5.0 Tapping/Left 2 10011 +1317.504 5.0 Tapping/Left 2 10293 +1354.624 5.0 Tapping/Right 3 10583 +1383.936 5.0 Control 1 10812 +1416.064 5.0 Tapping/Right 3 11063 +1456.0 5.0 Tapping/Right 3 11375 +1485.824 5.0 Tapping/Left 2 11608 +1513.344 5.0 Tapping/Right 3 11823 +1539.2 5.0 Tapping/Left 2 12025 +1565.44 5.0 Tapping/Right 3 12230 +1604.736 5.0 Tapping/Left 2 12537 +1638.016 5.0 Control 1 12797 +1673.6 5.0 Tapping/Right 3 13075 +1704.96 5.0 Control 1 13320 +1734.784 5.0 Control 1 13553 +1760.896 5.0 Tapping/Left 2 13757 +1791.104 5.0 Tapping/Right 3 13993 +1819.008 5.0 Tapping/Left 2 14211 +1857.536 5.0 Control 1 14512 +1896.832 5.0 Tapping/Left 2 14819 +1936.256 5.0 Control 1 15127 +1967.232 5.0 Tapping/Right 3 15369 +2003.328 5.0 Tapping/Right 3 15651 +2043.264 5.0 Tapping/Right 3 15963 +2070.144 5.0 Tapping/Left 2 16173 +2106.24 5.0 Tapping/Right 3 16455 +2132.992 5.0 Control 1 16664 +2166.784 5.0 Control 1 16928 +2198.4 5.0 Tapping/Right 3 17175 +2234.112 5.0 Tapping/Right 3 17454 +2270.976 5.0 Control 1 17742 +2305.536 5.0 Control 1 18012 +2334.848 5.0 Tapping/Right 3 18241 +2362.24 5.0 Tapping/Right 3 18455 +2393.216 5.0 Control 1 18697 +2432.64 5.0 Control 1 19005 +2466.304 5.0 Control 1 19268 +2505.216 5.0 Tapping/Left 2 19572 +2542.72 5.0 Tapping/Left 2 19865 +2578.432 5.0 Control 1 20144 +2606.336 5.0 Tapping/Left 2 20362 +2639.36 5.0 Tapping/Left 2 20620 +2677.504 5.0 Tapping/Right 3 20918 +2707.328 5.0 Control 1 21151 +2735.616 5.0 Control 1 21372 +2770.944 5.0 Tapping/Left 2 21648 +2806.912 5.0 Control 1 21929 +2833.408 5.0 Tapping/Left 2 22136 +2869.376 5.0 Control 1 22417 +2908.032 5.0 Tapping/Right 3 22719 +2938.496 5.0 Tapping/Left 2 22957 diff --git a/tests/test_transformers/test_transformers_munge_multi.m b/tests/test_transformers/test_transformers_munge_multi.m index cc58d31e..3522894e 100644 --- a/tests/test_transformers/test_transformers_munge_multi.m +++ b/tests/test_transformers/test_transformers_munge_multi.m @@ -104,8 +104,9 @@ function test_multi_complex_filter_with_and() % THEN assert(all(ismember({'Famous'; 'FirstRep'}, fieldnames(new_content)))); assertEqual(sum(strcmp(new_content.Famous, 'famous')), 52); - if ~bids.internal.is_octave - assertEqual(nansum(new_content.FirstRep), 52); + + if ~isempty(which('nansum')) + assertEqual(bids.internal.nansum(new_content.FirstRep), 52); end %% GIVEN diff --git a/tests/tests_utils/test_tsvread.m b/tests/tests_utils/test_tsvread.m index 456d5e48..48d190d3 100644 --- a/tests/tests_utils/test_tsvread.m +++ b/tests/tests_utils/test_tsvread.m @@ -37,6 +37,14 @@ function test_tsvread_basic() end +function test_tsvread_bug_552() + + tsv_file = fullfile(get_test_data_dir(), '..', 'data', 'bom_bug_552.tsv'); + content = bids.util.tsvread(tsv_file); + assert(ismember('onset', fieldnames(content))); + +end + function [pth, expected] = fixture() pth = fullfile(get_test_data_dir(), '..', 'data'); diff --git a/tests/utils/get_test_data_dir.m b/tests/utils/get_test_data_dir.m index 22f2713b..1a4d82cf 100644 --- a/tests/utils/get_test_data_dir.m +++ b/tests/utils/get_test_data_dir.m @@ -7,7 +7,7 @@ if strcmp(PLATFORM, 'GITHUB_ACTIONS') - data_dir = '/github/workspace/tests/'; + data_dir = fullfile('/', 'github', 'workspace', 'tests'); end diff --git a/tests/utils/validate_dataset.m b/tests/utils/validate_dataset.m index b2d8dc35..b5c71e07 100644 --- a/tests/utils/validate_dataset.m +++ b/tests/utils/validate_dataset.m @@ -1,5 +1,9 @@ function validate_dataset(bids_path) + if ispc + return + end + [sts, msg] = bids.validate(bids_path, '--config.ignore=99 --ignoreNiftiHeaders'); assertEqual(sts, 0);