diff --git a/.github/workflows/matlab.yml b/.github/workflows/matlab.yml new file mode 100644 index 00000000000..9db49bd018b --- /dev/null +++ b/.github/workflows/matlab.yml @@ -0,0 +1,89 @@ +name: MATLAB Tests Workflow + +on: + push: + pull_request: + schedule: + # * is a special character in YAML so you have to quote this string + # Execute a "nightly" build at 2 AM UTC + - cron: '0 2 * * *' + +jobs: + build-matlab-tests: + name: '[matlab:${{ matrix.matlab_version }}:${{ matrix.os }}]' + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash -l {0} + strategy: + fail-fast: false + matrix: + build_type: [Release] + os: [ubuntu-20.04] + matlab_version: [R2020a, R2020b, R2021a, latest] + + steps: + - uses: actions/checkout@v2 + + - uses: conda-incubator/setup-miniconda@v2 + with: + miniforge-variant: Mambaforge + miniforge-version: latest + channels: conda-forge,robotology + + - name: Setup MATLAB + uses: matlab-actions/setup-matlab@v1 + with: + release: ${{ matrix.matlab_version }} + + # workaround for https://github.com/robotology/robotology-superbuild/issues/64 + - name: Do not use MATLAB's stdc++ to avoid incompatibilities with other libraries + if: contains(matrix.os, 'ubuntu') + run: + echo 'LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libstdc++.so.6' >> $GITHUB_ENV + + - name: Dependencies + run: | + # Workaround for https://github.com/conda-incubator/setup-miniconda/issues/186 + conda config --remove channels defaults + # Compilation related dependencies + mamba install cmake compilers make ninja pkg-config + # Actual dependencies + mamba install eigen libxml2 assimp ipopt qt irrlicht osqp-eigen + + # Additional dependencies useful only on Linux + - name: Dependencies [Conda/Linux] + if: contains(matrix.os, 'ubuntu') + run: | + # Additional dependencies only useful on Linux + # See https://github.com/robotology/robotology-superbuild/issues/477 + mamba install expat-cos6-x86_64 freeglut libselinux-cos6-x86_64 libxau-cos6-x86_64 libxcb-cos6-x86_64 libxdamage-cos6-x86_64 libxext-cos6-x86_64 libxfixes-cos6-x86_64 libxxf86vm-cos6-x86_64 mesalib mesa-libgl-cos6-x86_64 + + - name: Configure + if: contains(matrix.os, 'macos') || contains(matrix.os, 'ubuntu') + run: | + mkdir build + cd build + cmake -GNinja -DCMAKE_INSTALL_PREFIX=${CONDA_PREFIX} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DMATLAB_FIND_DEBUG:BOOL=ON -DIDYNTREE_USES_MATLAB:BOOL=ON -DIDYNTREE_COMPILE_TESTS:BOOL=ON -DIDYNTREE_USES_QT5:BOOL=ON -DIDYNTREE_USES_ASSIMP:BOOL=ON -DIDYNTREE_USES_IPOPT:BOOL=ON -DIDYNTREE_USES_IRRLICHT:BOOL=ON -DIDYNTREE_USES_YARP:BOOL=OFF -DIDYNTREE_USES_ICUB_MAIN:BOOL=OFF -DIDYNTREE_USES_OSQPEIGEN:BOOL=ON .. + + - name: Build + run: | + cd build + cmake --build . --config ${{ matrix.build_type }} + + - name: Inspect libraries linked by iDynTreeMEX.mexa64 [Conda/Linux] + if: contains(matrix.os, 'ubuntu') + run: | + cd build + ldd ./lib/iDynTreeMEX.mexa64 + + - name: Test [Conda] + run: | + cd build + # Only run matlab tests as the rest of tests are already run by other jobs + ctest --output-on-failure -C ${{ matrix.build_type }} -R "matlab" -VV . + + - name: Install [Conda] + run: | + cd build + cmake --install . --config ${{ matrix.build_type }} diff --git a/bindings/matlab/tests/CMakeLists.txt b/bindings/matlab/tests/CMakeLists.txt index c8c3c57dfce..122e57baf5b 100644 --- a/bindings/matlab/tests/CMakeLists.txt +++ b/bindings/matlab/tests/CMakeLists.txt @@ -11,7 +11,7 @@ if (IDYNTREE_USES_MATLAB AND NOT IDYNTREE_DISABLE_MATLAB_TESTS) find_package(Matlab REQUIRED COMPONENTS MAIN_PROGRAM) add_test(NAME matlab_idyntree_tests - COMMAND ${Matlab_MAIN_PROGRAM} -nodisplay -nodesktop -nojvm -r "addpath('$');addpath('${MEX_BINDINGS_SOURCE_DIR}');addpath('${MATLAB_WRAPPERS_BINDINGS_SOURCE_DIR}');addpath('${CMAKE_CURRENT_SOURCE_DIR}/');addpath(genpath('${IDYNTREE_INTERNAL_MOXUNIT_PATH}'));success=moxunit_runtests('${CMAKE_CURRENT_SOURCE_DIR}','-verbose');exit(~success);") + COMMAND ${Matlab_MAIN_PROGRAM} -nodisplay -nodesktop -nojvm -batch "addpath('$');addpath('${MEX_BINDINGS_SOURCE_DIR}');addpath('${MATLAB_WRAPPERS_BINDINGS_SOURCE_DIR}');addpath('${CMAKE_CURRENT_SOURCE_DIR}/');addpath(genpath('${IDYNTREE_INTERNAL_MOXUNIT_PATH}'));success=moxunit_runtests('${CMAKE_CURRENT_SOURCE_DIR}','-verbose');exit(~success);") endif() if (IDYNTREE_USES_OCTAVE) diff --git a/bindings/matlab/tests/EKFTest.m b/bindings/matlab/tests/EKFTest.m index 3501a5e35d4..3c071f53eb2 100644 --- a/bindings/matlab/tests/EKFTest.m +++ b/bindings/matlab/tests/EKFTest.m @@ -1,4 +1,8 @@ function test_suite=EKFTest + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end initTestSuite function test_span diff --git a/bindings/matlab/tests/InertiaUnitTest.m b/bindings/matlab/tests/InertiaUnitTest.m index 2c89777e481..fe6ec900a37 100644 --- a/bindings/matlab/tests/InertiaUnitTest.m +++ b/bindings/matlab/tests/InertiaUnitTest.m @@ -1,4 +1,8 @@ function test_suite=InertiaUnitTest + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end initTestSuite function test_momentum_invariance diff --git a/bindings/matlab/tests/JointUnitTest.m b/bindings/matlab/tests/JointUnitTest.m index 438e0520e44..a28f9c19095 100644 --- a/bindings/matlab/tests/JointUnitTest.m +++ b/bindings/matlab/tests/JointUnitTest.m @@ -1,4 +1,8 @@ function test_suite=JointUnitTest + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end initTestSuite function test_joint_constructor diff --git a/bindings/matlab/tests/MatrixUnitTest.m b/bindings/matlab/tests/MatrixUnitTest.m index 3a85c1d97e0..83f2e29d4fc 100644 --- a/bindings/matlab/tests/MatrixUnitTest.m +++ b/bindings/matlab/tests/MatrixUnitTest.m @@ -1,4 +1,8 @@ function test_suite=MatrixUnitTest + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end initTestSuite function test_sparse_matrices diff --git a/bindings/matlab/tests/PositionUnitTest.m b/bindings/matlab/tests/PositionUnitTest.m index 95a7d3bbc9f..dda40910020 100644 --- a/bindings/matlab/tests/PositionUnitTest.m +++ b/bindings/matlab/tests/PositionUnitTest.m @@ -1,4 +1,8 @@ function test_suite=PositionUnitTest + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end initTestSuite test_sum_of_positions diff --git a/bindings/matlab/tests/TransformUnitTest.m b/bindings/matlab/tests/TransformUnitTest.m index 3d7375542a1..33cb7316ed5 100644 --- a/bindings/matlab/tests/TransformUnitTest.m +++ b/bindings/matlab/tests/TransformUnitTest.m @@ -1,4 +1,8 @@ function test_suite=TransformUnitTest + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end initTestSuite function test_pos_twist_wrench_invariance diff --git a/bindings/matlab/tests/highLevelWrappersSmokeTest.m b/bindings/matlab/tests/highLevelWrappersSmokeTest.m index 293bfd37315..488d55f3fef 100644 --- a/bindings/matlab/tests/highLevelWrappersSmokeTest.m +++ b/bindings/matlab/tests/highLevelWrappersSmokeTest.m @@ -1,4 +1,8 @@ function test_suite = highLevelWrappersSmokeTest + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end initTestSuite function test__high_level_wrappers diff --git a/extern/MOxUnit/.github/workflows/CI.yml b/extern/MOxUnit/.github/workflows/CI.yml new file mode 100644 index 00000000000..d7553111942 --- /dev/null +++ b/extern/MOxUnit/.github/workflows/CI.yml @@ -0,0 +1,44 @@ +name: CI + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master branch +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "unit-tests" + unit-tests: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + # Use A Github Action to perform tests + - name: run unit tests and documentation tests, generate coverage report + uses: joergbrech/moxunit-action@v1.1 + with: + tests: tests + src: MOxUnit + with_coverage: true + doc_tests: true + cover_xml_file: coverage.xml + + # Store the coverage report as an artifact + - name: Store Coverage report as artifact + uses: actions/upload-artifact@v1 + with: + name: coverage_xml_file + path: coverage.xml + + ## Use a Github Action to publish coverage reports + #- name: Publish coverage report to codecov.io + # uses: codecov/codecov-action@v1 + # with: + # file: ./coverage.xml diff --git a/extern/MOxUnit/.gitignore b/extern/MOxUnit/.gitignore new file mode 100644 index 00000000000..8adc86c055a --- /dev/null +++ b/extern/MOxUnit/.gitignore @@ -0,0 +1,14 @@ +*.DS_Store +*.swp +*.css +*.m~ +data +datadb +datadb.zip +.git.bfg-report +*.md.sw? +octave-workspace +git_log.txt +git_summary.txt +*.pyc +*.asv diff --git a/extern/MOxUnit/.travis.yml b/extern/MOxUnit/.travis.yml new file mode 100644 index 00000000000..3736045be2a --- /dev/null +++ b/extern/MOxUnit/.travis.yml @@ -0,0 +1,82 @@ +# vim ft=yaml +# travis-ci.org definition for MOxUnit build (based on CoSMoMVPA, +# which is based on PyMVPA, which is based on nipype configuration, +# which in turn was based on nipy) + +language: minimal +os: linux + +cache: + - apt + +env: + matrix: + - WITH_COVERAGE= + - WITH_COVERAGE=true + - RUN_DOC_TEST=true + +before_install: + # to prevent IPv6 being used for APT + - sudo bash -c "echo 'Acquire::ForceIPv4 \"true\";' > /etc/apt/apt.conf.d/99force-ipv4" + - travis_retry sudo apt-get -y -qq update + - travis_retry sudo apt-get install -y -qq software-properties-common python-software-properties + - travis_retry sudo apt-add-repository -y ppa:octave/stable + - travis_retry sudo apt-get -y -qq update + # get Octave 4,0 + - travis_retry sudo apt-get -y -qq install octave liboctave-dev + # install MOcov + - cd .. + - rm -rf MOcov + - git clone https://github.com/MOcov/MOcov.git + - make -C MOcov install + # retrieve MOdox + - rm -rf MOdox + - travis_retry git clone -v git://github.com/MOdox/MOdox.git + - make -C MOdox install + # go back to original directory + - cd MOxUnit + # prevent shippable from re-using old test results + - if [[ "$SHIPPABLE" == "true" ]]; then + if [[ "$WITH_COVERAGE" != "true" ]]; then + rm -f ${SHIPPABLE_BUILD_DIR}/shippable/testresults/*.xml; + fi; + fi + +script: + - if [[ "$WITH_COVERAGE" == "true" ]]; then + TEST_ARGS=WITH_COVERAGE=true; + COVER_ARGS=COVER=`pwd`/MOxUnit; + + if [[ "$SHIPPABLE" == "true" ]]; then + OUTPUT_ARGS=COVER_XML_FILE=${SHIPPABLE_BUILD_DIR}/shippable/codecoverage/coverage.xml; + AFTER_SCRIPT="find ${SHIPPABLE_BUILD_DIR}/shippable/;cat ${SHIPPABLE_BUILD_DIR}/shippable/codecoverage/coverage.xml;which reports"; + elif [[ "$TRAVIS" == "true" ]]; then + OUTPUT_ARGS=COVER_JSON_FILE=`pwd`/coveralls.json; + AFTER_SCRIPT="curl --verbose -F json_file=@`pwd`/coveralls.json https://coveralls.io/api/v1/jobs"; + fi; + elif [[ "$SHIPPABLE" == "true" ]]; then + RESULT_ARGS=JUNIT_XML_FILE=${SHIPPABLE_BUILD_DIR}/shippable/testresults/test_results.xml; + elif [[ "$RUN_DOC_TEST" == "true" ]]; then + TEST_ARGS=RUN_DOC_TEST=true; + fi; + + - echo Test arguments $TEST_ARGS $COVER_ARGS $OUTPUT_ARGS $RESULT_ARGS + - make test $TEST_ARGS $COVER_ARGS $OUTPUT_ARGS $RESULT_ARGS + - eval $AFTER_SCRIPT + +jobs: + include: + - language: matlab + matlab: R2020a + + # No need to do anything, but the 'before_install' key is inherited from teh enclosing scope and has to be reset to something insubstantial + before_install: + - echo 'noop' + # Could also clear 'env' but, as is, the only environment variable inhereted fromthe scope is $WITH_COVERAGE which does nothing here + + script: + - if [[ "$SHIPPABLE" == "true" ]]; then + echo 'No Matlab testing on Shippable'; + else + matlab -batch 'back=cd("./MOxUnit/"); moxunit_set_path(); cd(back); moxunit_runtests tests -verbose; exit(double(~ans))'; + fi; diff --git a/extern/MOxUnit/COPYING b/extern/MOxUnit/COPYING index 492016c6c63..2a008b93234 100644 --- a/extern/MOxUnit/COPYING +++ b/extern/MOxUnit/COPYING @@ -1,6 +1,6 @@ (The MIT License) -Copyright (c) 2015 Nikolaas N. Oosterhof +Copyright (c) 2015-2017 Nikolaas N. Oosterhof Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/extern/MOxUnit/MOxUnit/@MOxUnitFunctionHandleTestCase/run.m b/extern/MOxUnit/MOxUnit/@MOxUnitFunctionHandleTestCase/run.m index 2a9e8b15c25..9525f6f865e 100644 --- a/extern/MOxUnit/MOxUnit/@MOxUnitFunctionHandleTestCase/run.m +++ b/extern/MOxUnit/MOxUnit/@MOxUnitFunctionHandleTestCase/run.m @@ -16,12 +16,19 @@ % % NNO 2015 + original_warning_state=warning('query'); + warning_state_resetter=onCleanup(@()warning(original_warning_state)); + start_tic = tic; try passed=false; try - obj.function_handle(); + if nargin(obj.function_handle) > 0 + obj.function_handle(obj); + else + obj.function_handle(); + end passed=true; catch e=lasterror(); @@ -39,7 +46,11 @@ test_outcome_args={reason}; else test_outcome_constructor=@MOxUnitFailedTestOutcome; - test_outcome_args={e}; + + % trim the stack so that all test case machinery is + % removed from the stack + e_trimmed=trim_stack(e); + test_outcome_args={e_trimmed}; end end @@ -56,4 +67,29 @@ test_outcome = test_outcome_constructor(obj, toc(start_tic), ... test_outcome_args{:}); - report = reportTestOutcome(report, test_outcome); \ No newline at end of file + report = reportTestOutcome(report, test_outcome); + + +function e_trimmed=trim_stack(e) +% trim the stack from e.stack, so that everything up to and including the +% first (nearest to the calling root) entry is removed + stack=e.stack; + + n_stack=numel(stack); + + this_file=sprintf('%s.m',mfilename('fullpath')); + + for pos=n_stack:-1:1 + if strcmp(stack(pos).file,this_file) + % found first match with this filename, now trim this function + % and their callig functions + trimmed_stack=stack(1:(pos-1)); + + e_trimmed=e; + e_trimmed.stack=trimmed_stack; + return; + end + end + + assert(false,'This should not happen'); + diff --git a/extern/MOxUnit/MOxUnit/@MOxUnitMatlabUnitWrapperTestCase/MOxUnitMatlabUnitWrapperTestCase.m b/extern/MOxUnit/MOxUnit/@MOxUnitMatlabUnitWrapperTestCase/MOxUnitMatlabUnitWrapperTestCase.m new file mode 100644 index 00000000000..571cac9faa6 --- /dev/null +++ b/extern/MOxUnit/MOxUnit/@MOxUnitMatlabUnitWrapperTestCase/MOxUnitMatlabUnitWrapperTestCase.m @@ -0,0 +1,24 @@ +function obj=MOxUnitMatlabUnitWrapperTestCase(matlab_unittest_test_obj) +% instantiates wrapper around matlab.unittest.Test class +% +% obj=MOxUnitMatlabUnitWrapperTestCase(matlab_unittest_test_obj) +% +% Inputs: +% matlab_unittest_test_obj singleton matlab.unittest.Test object +% +% Output: +% obj MOxUnitMatlabUnitWrapperTestCase instance +% containing the test + if ~(isa(matlab_unittest_test_obj, 'matlab.unittest.Test') && ... + numel(matlab_unittest_test_obj)==1) + error('Input must be singleton matlab.unittest.Test class'); + end + + name=matlab_unittest_test_obj.Name; + location=name; + + s=struct(); + s.matlab_unittest_test_obj=matlab_unittest_test_obj; + + obj=class(s,'MOxUnitMatlabUnitWrapperTestCase',... + MOxUnitTestCase(name,location)); diff --git a/extern/MOxUnit/MOxUnit/@MOxUnitMatlabUnitWrapperTestCase/run.m b/extern/MOxUnit/MOxUnit/@MOxUnitMatlabUnitWrapperTestCase/run.m new file mode 100644 index 00000000000..534fc1d3a62 --- /dev/null +++ b/extern/MOxUnit/MOxUnit/@MOxUnitMatlabUnitWrapperTestCase/run.m @@ -0,0 +1,59 @@ +function report=run(obj,report) +% Run test associated with MOxUnitMatlabUnitWrapperTestCase +% +% report=run(obj,report) +% +% Inputs: +% obj MOxUnitMatlabUnitWrapperTestCase object +% report MoxUnitTestReport instance to which test results are to +% be reported. +% +% Output: +% report MoxUnitTestReport containing tests results +% after running the test associated with obj. +% +% See also: MoxUnitTestReport +% + + start_tic = tic; + + try + runner=matlab.unittest.TestRunner.withNoPlugins(); + runner.addPlugin(matlab.unittest.plugins.DiagnosticsRecordingPlugin); + result=runner.run(obj.matlab_unittest_test_obj); + + if isequal(result.Passed,1) + test_outcome_constructor=@MOxUnitPassedTestOutcome; + test_outcome_args={}; + else + try + test_outcome_constructor=@MOxUnitFailedTestOutcome; + test_outcome_args={make_failed_error_from_result(obj, result)}; + catch + % everything else fails + test_outcome_constructor=@MOxUnitFailedTestOutcome; + test_outcome_args={make_failed_error(obj)}; + end + end + catch + e=lasterror(); + test_outcome_constructor=@MOxUnitErroredTestOutcome; + test_outcome_args={e}; + end + + test_outcome = test_outcome_constructor(obj, toc(start_tic), ... + test_outcome_args{:}); + + report = reportTestOutcome(report, test_outcome); + +function e=make_failed_error_from_result(obj, result) + e=struct(); + e.message=result.Details.DiagnosticRecord.Exception.message; + e.identifier=result.Details.DiagnosticRecord.Exception.identifier; + e.stack=result.Details.DiagnosticRecord.Stack; + +function e=make_failed_error(obj) + e=struct(); + e.message=sprintf('Test failed: %s', str(obj)); + e.identifier=''; + e.stack=struct('file',{},'name',{},'line',{}); \ No newline at end of file diff --git a/extern/MOxUnit/MOxUnit/@MOxUnitTestCase/disp.m b/extern/MOxUnit/MOxUnit/@MOxUnitTestCase/disp.m new file mode 100644 index 00000000000..4a7830c9020 --- /dev/null +++ b/extern/MOxUnit/MOxUnit/@MOxUnitTestCase/disp.m @@ -0,0 +1,11 @@ +function disp(obj) +% display MoxUnitFunctionHandleTestCase object +% +% disp(obj) +% +% Inputs: +% obj MoxUnitFunctionHandleTestCase object +% +% NNO 2015 + + disp(str(obj)); diff --git a/extern/MOxUnit/MOxUnit/@MOxUnitTestCase/str.m b/extern/MOxUnit/MOxUnit/@MOxUnitTestCase/str.m index 3055d001ae1..cb6c7f8db98 100644 --- a/extern/MOxUnit/MOxUnit/@MOxUnitTestCase/str.m +++ b/extern/MOxUnit/MOxUnit/@MOxUnitTestCase/str.m @@ -9,4 +9,5 @@ % Output: % s string representation of obj. % - s=sprintf('', class(obj)); + + s=sprintf('%s(%s,%s)>', class(obj), getName(obj), getLocation(obj)); diff --git a/extern/MOxUnit/MOxUnit/@MOxUnitTestCase/subsref.m b/extern/MOxUnit/MOxUnit/@MOxUnitTestCase/subsref.m new file mode 100644 index 00000000000..4a0036f59f3 --- /dev/null +++ b/extern/MOxUnit/MOxUnit/@MOxUnitTestCase/subsref.m @@ -0,0 +1,26 @@ +function r = subsref(val, idx) + methodList = {... + 'assertElementsAlmostEqual',... + 'assertEqual',... + 'assertError',... + 'assertExceptionThrown',... + 'assertFalse',... + 'assertGreaterThan',... + 'assertLessThan',... + 'assertTrue',... + 'assertVectorsAlmostEqual',... + 'assertWarning'}; + + r = []; + type = idx.type; + subs = idx.subs; + + for i=methodList + if (strcmp(type, '.') && strcmp(subs, i{:})) + r = eval(['@', subs]); + end + end + + if isempty(r) + error('moxunit:undefinedMethod', ['Undefined method: ', subs]); + end diff --git a/extern/MOxUnit/MOxUnit/@MOxUnitTestOutcome/getProgressStr.m b/extern/MOxUnit/MOxUnit/@MOxUnitTestOutcome/getProgressStr.m index 35772e40dbb..5c7268b1bac 100644 --- a/extern/MOxUnit/MOxUnit/@MOxUnitTestOutcome/getProgressStr.m +++ b/extern/MOxUnit/MOxUnit/@MOxUnitTestOutcome/getProgressStr.m @@ -13,7 +13,10 @@ outcome=getOutcomeStr(obj, verbosity); if verbosity>=2 - string=sprintf('%10s: %s\n', outcome, str(getTest(obj))); + test_node=getTest(obj); + string=sprintf('%10s in % 7.2f s: %s\n', ... + outcome, getDuration(obj), ... + str(test_node)); elseif verbosity>=1 string=outcome; else diff --git a/extern/MOxUnit/MOxUnit/@MOxUnitTestOutcome/getSummaryStr.m b/extern/MOxUnit/MOxUnit/@MOxUnitTestOutcome/getSummaryStr.m index abce9757a77..f47c22ad21f 100644 --- a/extern/MOxUnit/MOxUnit/@MOxUnitTestOutcome/getSummaryStr.m +++ b/extern/MOxUnit/MOxUnit/@MOxUnitTestOutcome/getSummaryStr.m @@ -18,8 +18,10 @@ if isstruct(content) % error or failure + stack_prefix=' '; + stack_str=moxunit_util_stack2str(content.stack, stack_prefix); str=sprintf('%s: %s\n%s',... - outcome,content.message,stack2str(content.stack)); + outcome,content.message,stack_str); elseif ischar(content) % skipped @@ -63,9 +65,12 @@ raw_message=content.message; message=moxunit_util_remove_matlab_anchor_tag(raw_message); message=moxunit_util_escape_xml(message); - stack_trace=moxunit_util_escape_xml(stack2str(content.stack)); + + raw_stack_str=moxunit_util_stack2str(content.stack); + stack_str=moxunit_util_escape_xml(raw_stack_str); + infix=sprintf('>\n <%s message="%s">%s',... - outcome,message,stack_trace,outcome); + outcome,message,stack_str,outcome); end suffix=sprintf('\n'); @@ -73,15 +78,7 @@ str=sprintf('%s%s%s',prefix,infix,suffix); -function str=stack2str(stack) - n_stack=numel(stack); - lines=cell(1,n_stack); - for k=1:n_stack - s=stack(k); - lines{k}=sprintf(' %s:%d (%s)', ... - s.name, s.line, s.file); - end - str=moxunit_util_strjoin(lines,'\n'); + function s=get_classname(test_) location=getLocation(test_); diff --git a/extern/MOxUnit/MOxUnit/@MOxUnitTestReport/getStream.m b/extern/MOxUnit/MOxUnit/@MOxUnitTestReport/getStream.m new file mode 100644 index 00000000000..ad0c2b47a45 --- /dev/null +++ b/extern/MOxUnit/MOxUnit/@MOxUnitTestReport/getStream.m @@ -0,0 +1,13 @@ +function stream=getStream(obj) +% get the output stream of the report +% +% stream=getStream(obj) +% +% Input: +% obj MOxUnitTestReport instance +% +% Output: +% stream File identifier to which output is written. +% stream=1 means standard output + + stream=obj.stream; diff --git a/extern/MOxUnit/MOxUnit/@MOxUnitTestReport/getTestOutputStatistics.m b/extern/MOxUnit/MOxUnit/@MOxUnitTestReport/getTestOutputStatistics.m index 2612110779d..32b755850c3 100644 --- a/extern/MOxUnit/MOxUnit/@MOxUnitTestReport/getTestOutputStatistics.m +++ b/extern/MOxUnit/MOxUnit/@MOxUnitTestReport/getTestOutputStatistics.m @@ -9,22 +9,16 @@ % % Output: % label_counts struct that may contain the fields 'failure', 'error', -% 'skipped', with the value the number of times a text -% failed, raised an error or was skipped. If a field is -% absent, the number of times is zero. -% A success is not part of label_counts +% 'skipped', or 'passed' with the value the number of +% times a test failed, raised an error, was skipped or +% passed (respectively). If a field is absent, the number +% of times is zero. label_verbosity=2; label_counts=struct(); for i=1:countTestOutcomes(obj) test_outcome=getTestOutcome(obj,i); - - if isSuccess(test_outcome) - % no need to report statistics for successes - continue; - end - test_label=getOutcomeStr(test_outcome,label_verbosity); if ~isfield(label_counts,test_label); diff --git a/extern/MOxUnit/MOxUnit/@MOxUnitTestReport/getVerbosity.m b/extern/MOxUnit/MOxUnit/@MOxUnitTestReport/getVerbosity.m new file mode 100644 index 00000000000..c25237dc72e --- /dev/null +++ b/extern/MOxUnit/MOxUnit/@MOxUnitTestReport/getVerbosity.m @@ -0,0 +1,13 @@ +function verbosity=getVerbosity(obj) +% get the verbosity of the report +% +% verbosity=getVerbosity(obj) +% +% Input: +% obj MOxUnitTestReport instance +% +% Output: +% verbosity Integer indicating how verbose the report is; +% higher values means more verbose + + verbosity=obj.verbosity; diff --git a/extern/MOxUnit/MOxUnit/@MOxUnitTestReport/writeXML.m b/extern/MOxUnit/MOxUnit/@MOxUnitTestReport/writeXML.m new file mode 100644 index 00000000000..76c8fe1f811 --- /dev/null +++ b/extern/MOxUnit/MOxUnit/@MOxUnitTestReport/writeXML.m @@ -0,0 +1,23 @@ +function writeXML(obj,fn) +% write test report in JUnit XML format +% +% writeXML(obj,fn) +% +% Inputs: +% obj MOxUnitTestReport instance +% fn filename to which results are written +% + + fid=fopen(fn,'w'); + file_closer=onCleanup(@()fclose(fid)); + + xml_preamble=''; + xml_header=''; + xml_body=getSummaryStr(obj,'xml'); + xml_footer=''; + + fprintf(fid,'%s\n%s\n%s\n%s',... + xml_preamble,... + xml_header,... + xml_body,... + xml_footer); \ No newline at end of file diff --git a/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/MOxUnitTestSuite.m b/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/MOxUnitTestSuite.m index a07321d37b2..1dae00ab6eb 100644 --- a/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/MOxUnitTestSuite.m +++ b/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/MOxUnitTestSuite.m @@ -19,7 +19,8 @@ end s=struct(); - s.tests=cell(0); + s.test_count=0; + s.tests=cell(10,1); % some space to start with obj=class(s,class_name,MOxUnitTestNode(name)); diff --git a/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/addFromDirectory.m b/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/addFromDirectory.m index d12d00db7d5..9c5eae942bc 100644 --- a/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/addFromDirectory.m +++ b/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/addFromDirectory.m @@ -7,8 +7,8 @@ % obj MoxUnitTestSuite instance. % directory name of directory that may contain files with top-level % function that returns MOxUnitTestNode instances. -% pat File pattern to look for in directory (default: '*.m'). -% Matching files are added using addFromFile. +% pattern Regular expression containing file pattern of files to +% add. % add_recursive If true, then files are added recursively from % sub-directories. If false (the default) then only files % are added from directory, but files in subdirectories @@ -18,9 +18,6 @@ % obj MoxUnitTestSuite instance with MOxUnitTestNode % instances added, if present in the files in directory. % -% Notes: -% - this function does not add files recursively. -% % See also: initTestSuite, addFromFile % % NNO 2015 @@ -29,33 +26,12 @@ add_recursive=false; end - if nargin<3 || isempty(pat) - pat='*.m'; - end - - pat_regexp=['^' regexptranslate('wildcard',pat) '$']; - - if isdir(directory) - d=dir(directory); - n=numel(d); + % find the files + filenames=moxunit_util_find_files(directory,pat,add_recursive); - for k=1:n - fn=d(k).name; - path_fn=fullfile(directory,fn); + n_filenames=numel(filenames); - if isdir(path_fn) - if add_recursive && is_sub_directory(fn) - obj=addFromDirectory(obj,path_fn,pat,add_recursive); - end - elseif ~isempty(regexp(fn,pat_regexp,'once')) - obj=addFromFile(obj,path_fn); - end - end - else - error('moxunit:illegalParameter','Input is not a directory'); + for k=1:n_filenames + filename=filenames{k}; + obj=addFromFile(obj,filename); end - - - -function tf=is_sub_directory(fn) - tf=~any(strcmp(fn,{'.','..'})); diff --git a/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/addFromFile.m b/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/addFromFile.m index bffb05fb1ff..20e2a1c0c13 100644 --- a/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/addFromFile.m +++ b/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/addFromFile.m @@ -12,9 +12,7 @@ % % Output: % obj MoxUnitTestSuite instance with the MOxUnitTestNode test -% added, if present. If calling the function defined in -% fn raises an exception, or does not return a -% MOxUnitTestNode instance, then obj remains unchanged. +% added, if present. % % See also: initTestSuite % @@ -24,22 +22,31 @@ cleaner=onCleanup(@()cd(orig_pwd)); [fn_dir,name]=fileparts(fn); + if ~isempty(fn_dir) cd(fn_dir); end - try - func=str2func(name); - - if nargout(func)~=1 - return; - end + func=str2func(name); - test_case=func(); - if isa(test_case,'MOxUnitTestNode') - obj=addTest(obj,test_case); - end - catch - % ignore silently, assuming that the file was not a test case + if nargout(func)~=1 + return; end + test_case=func(); + if isa(test_case,'MOxUnitTestNode') + obj=addTest(obj,test_case); + + elseif isa(test_case,'matlab.unittest.Test') + + matlab_test_struct=test_case; + n_tests=numel(matlab_test_struct); + + for k=1:n_tests + test_node=MOxUnitMatlabUnitWrapperTestCase(... + matlab_test_struct(k)); + obj=addTest(obj,test_node); + end + else + % unknown class, silent skip + end \ No newline at end of file diff --git a/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/addFromSuite.m b/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/addFromSuite.m index a5628c1fd9c..70ce47decd5 100644 --- a/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/addFromSuite.m +++ b/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/addFromSuite.m @@ -16,12 +16,9 @@ % % NNO 2015 - n_obj=countTestNodes(obj); - n_s=countTestNodes(s); + n_test_nodes=countTestNodes(s); - % allocate space - obj.tests{end+n_s}=[]; - - for k=1:n_s - obj.tests{n_obj+k}=getTestNode(s,k); + for k=1:n_test_nodes + test_node=getTestNode(s, k); + obj=addTest(obj, test_node); end diff --git a/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/addTest.m b/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/addTest.m index fd99dc41014..4a26281ca00 100644 --- a/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/addTest.m +++ b/extern/MOxUnit/MOxUnit/@MOxUnitTestSuite/addTest.m @@ -14,5 +14,21 @@ % See also: initTestSuite % % NNO 2015 + n_tests=numel(obj.tests); - obj.tests{end+1}=t; + obj_tests_too_small=n_tests+1 < obj.test_count; + + if obj_tests_too_small + % double the size every time obj.tests is too small. This means + % that adding N tests requires that obj.tests is resized + % only ceil(log2(N)) times + empty_idx=2*n_tests+1; + assert(numel(obj.tests)max_i + error('index %d exceeds the number of tests %d',i,max_i); + end + + obj.tests{i}=t; + + +function tf=is_scalar_integer(i) + tf=isnumeric(i) && ... + numel(i)==1 && ... + i>0 && ... + round(i)==i; + diff --git a/extern/MOxUnit/MOxUnit/assertElementsAlmostEqual.m b/extern/MOxUnit/MOxUnit/assertElementsAlmostEqual.m index 0b8d39cee7c..f9957792b34 100644 --- a/extern/MOxUnit/MOxUnit/assertElementsAlmostEqual.m +++ b/extern/MOxUnit/MOxUnit/assertElementsAlmostEqual.m @@ -20,6 +20,19 @@ function assertElementsAlmostEqual(a,b,varargin) % 'moxunit:floatsDiffer' values in a and b are not % almost equal (see note below) % +% Examples: +% assertElementsAlmostEqual([1 2; 3 4],[1 2; 3 4]); +% %|| % no error +% +% assertElementsAlmostEqual(1,1.0001); +% %|| error('inputs are not equal within relative tolerance 1.49011e-08') +% +% assertElementsAlmostEqual(1,1.0001,'absolute',1e-3); +% %|| % no error +% +% assertElementsAlmostEqual('foo','bar') +% %|| error('first input is not float'); +% % Notes: % - If tol_type is 'relative', a and b are almost equal if % @@ -49,9 +62,9 @@ function assertElementsAlmostEqual(a,b,varargin) full_message=moxunit_util_input2str(message,whatswrong,a,b); if moxunit_util_platform_is_octave() - error(error_id,full_message); + error(error_id,'%s',full_message); else - throwAsCaller(MException(error_id, full_message)); + throwAsCaller(MException(error_id,'%s',full_message)); end diff --git a/extern/MOxUnit/MOxUnit/assertEqual.m b/extern/MOxUnit/MOxUnit/assertEqual.m index cc08d4772ec..523590ee166 100644 --- a/extern/MOxUnit/MOxUnit/assertEqual.m +++ b/extern/MOxUnit/MOxUnit/assertEqual.m @@ -9,11 +9,21 @@ function assertEqual(a, b, message) % msg optional custom message % % Raises: -% 'moxunit:differentSize' a and b are of different size -% 'moxunit:differentClass a and b are of different class -% 'moxunit:differentSparsity' a is sparse and b is not, or -% vice versa -% 'moxunit:elementsNotEqual' values in a and b are not equal +% 'assertEqual:nonEqual' a and b are of different +% size or values are not equal +% 'assertEqual:classNotEqual a and b are of different class +% 'assertEqual:sparsityNotEqual' a is sparse and b is not, or +% vice versa +% +% Examples: +% assertEqual('foo','foo'); +% %|| % passes without output +% +% assertEqual('foo','bar'); +% %|| error('elements are not equal'); +% +% assertEqual([1 2],[1;2]); +% %|| error('inputs are not of the same size'); % % Notes: % - If a custom message is provided, then any error message is prefixed @@ -57,20 +67,21 @@ function assertEqual(a, b, message) full_message=moxunit_util_input2str(message,whatswrong,a,b); if moxunit_util_platform_is_octave() - error(error_id,full_message); + error(error_id,'%s',full_message); else - throwAsCaller(MException(error_id, full_message)); + throwAsCaller(MException(error_id, '%s', full_message)); end function tf=isequaln_wrapper(a,b) % wrapper to support old versions of Matlab persistent has_equaln; - if isequal(has_equaln,true) + if isempty(has_equaln) + has_equaln=~isempty(which('isequaln')); + end + + if has_equaln tf=isequaln(a,b); - elseif isequal(has_equaln,false) - tf=isequalwithequalnans(a,b); else - has_equaln=~isempty(which('isequaln')); - tf=isequaln_wrapper(a,b); + tf=isequalwithequalnans(a,b); end diff --git a/extern/MOxUnit/MOxUnit/assertError.m b/extern/MOxUnit/MOxUnit/assertError.m new file mode 100644 index 00000000000..65f3ec70546 --- /dev/null +++ b/extern/MOxUnit/MOxUnit/assertError.m @@ -0,0 +1,2 @@ +function assertError(varargin) + assertExceptionThrown(varargin{:}); \ No newline at end of file diff --git a/extern/MOxUnit/MOxUnit/assertExceptionThrown.m b/extern/MOxUnit/MOxUnit/assertExceptionThrown.m index 1ff8ea23a87..ed885cd3149 100644 --- a/extern/MOxUnit/MOxUnit/assertExceptionThrown.m +++ b/extern/MOxUnit/MOxUnit/assertExceptionThrown.m @@ -9,7 +9,10 @@ function assertExceptionThrown(func, expected_id, message) % [varargout{:}] = func() % expected_id Identifier of the expected exception (optional). If % not provided, then func can raise any exception. -% To allow for any exception to be raised, use '*' +% To allow for any exception to be raised, use '*'. +% Multiple ids can be provided in a cell string; the +% assertion means that at least one id matches the one +% that is thrown. % message Custom message to be included when func fails to throw % % Throws: @@ -18,15 +21,28 @@ function assertExceptionThrown(func, expected_id, message) % 'moxunit:wrongExceptionRaised' func() does raise an exception but with % an identifier different from % expected_id +% 'moxunit:illegalParameter' the input arguments are of the wrong +% type or content % % % Examples: % % Assert that sin will throw an exception when its argument is a struct -% >> assertExceptionThrown( @()sin( struct([]) ) ) +% assertExceptionThrown( @()sin( struct([]) )); +% %|| % ok % -% % Assert that sin throws AND that the ID is 'MATLAB:UndefinedFunction' -% >> assertExceptionThrown( @()sin( struct([]) ), ... -% 'MATLAB:UndefinedFunction') +% % Assert that sin throws an exception AND that the ID is +% % either '' or 'MATLAB:UndefinedFunction' +% allowed_ids={'','MATLAB:UndefinedFunction'}; +% assertExceptionThrown( @()sin( struct([]) ), allowed_ids); +% +% % No exception raised +% assertExceptionThrown(@()disp('hello world')); +% %|| error('No exception was raised'); +% +% % Wrong exception raised +% assertExceptionThrown(@()[1 2]*[1 2 3],'MATLAB:UndefinedFunction') +% %|| error(['exception ''MATLAB:innerdim'' was raised, '... +% %|| 'expected ''MATLAB:UndefinedFunction''.']) % % Notes: % - This function allows one to test for exceptions being thrown, and @@ -65,9 +81,9 @@ function assertExceptionThrown(func, expected_id, message) full_message=moxunit_util_input2str(message,whats_wrong); if moxunit_util_platform_is_octave() - error(id,full_message); + error(id,'%s',full_message); else - throwAsCaller(MException(id, full_message)); + throwAsCaller(MException(id,'%s',full_message)); end @@ -75,43 +91,77 @@ function assertExceptionThrown(func, expected_id, message) id = 'moxunit:exceptionNotRaised'; whats_wrong='No exception was raised'; - if ~strcmp(expected_id,'*') + if ~any(strcmp(expected_id,'*')) % add suffix with expected id - whats_wrong=sprintf('%s, expected exception ''%s''',... - whats_wrong,expected_id); + whats_wrong=sprintf('%s, expected exception %s',... + whats_wrong,expected_id2str(expected_id)); end function [id,whatswrong]=wrong_exception_raised(found_id, expected_id) + last_err=lasterror(); + stack=last_err.stack; + + stack_indent_length=8; + stack_indent=repmat(' ',1,stack_indent_length); + %stack_indent=' | ' + stack_trace_str=moxunit_util_stack2str(stack,stack_indent); + id='moxunit:wrongExceptionRaised'; whatswrong = sprintf(... - 'exception ''%s'' was raised, expected ''%s''',... - found_id, expected_id); + ['exception ''%s'' was raised, expected %s. '... + 'Stack trace:\n\n%s'],... + found_id, expected_id2str(expected_id), stack_trace_str); + +function expected_str=expected_id2str(expected_id) + if iscellstr(expected_id) && numel(expected_id)>1 + expected_str=sprintf('one of ''%s''',... + moxunit_util_strjoin(expected_id,''', ''')); + else + if iscellstr(expected_id) + expected_id=expected_id{1}; + end + expected_str=sprintf('''%s''',expected_id); + end function verify_inputs(varargin) - expected_types={'function_handle','char','char'}; - for k=1:numel(expected_types) - expected_type=expected_types{k}; - if ~isa(varargin{k},expected_type) + expected_type_funcs={@(x)isa(x,'function_handle'),... + @(x)ischar(x) || iscellstr(x),... + @ischar,... + }; + for k=1:numel(expected_type_funcs) + expected_type_func=expected_type_funcs{k}; + arg=varargin{k}; + if ~expected_type_func(arg) error('moxunit:illegalParameter',... - ['Argument at position %d must be '... - 'of type ''%s'', found ''%s'''],... - k,expected_type,class(varargin{k})); + ['Argument at position %d of wrong type, '... + 'found ''%s'''],... + k,class(varargin{k})); end end expected_id=varargin{2}; - if ~(strcmp(expected_id,'') || ... - strcmp(expected_id,'*') || ... - moxunit_util_is_message_identifier(expected_id)) + if ~is_valid_error_id(expected_id) error('moxunit:illegalParameter',... ['Argument at position 2, ''expected_id'', '... - 'must be empty, an asterisk (''*''), or '... + 'must be a cellstring or single string; each '... + 'string must be empty, an asterisk (''*''), or '... 'a valid message identifier, found ''%s'''],... expected_id); end +function tf=is_valid_error_id(expected_id) + if iscellstr(expected_id) + % recursive call + tf=all(cellfun(@is_valid_error_id,expected_id)); + return + end + + tf=strcmp(expected_id,'') ... + || strcmp(expected_id,'*') ... + || iscellstr(expected_id) ... + || moxunit_util_is_message_identifier(expected_id); @@ -119,5 +169,12 @@ function verify_inputs(varargin) tf=strcmp(id,'*'); function tf=exception_id_matches(expected_id,found_id) - tf=is_wildcard_id(expected_id) || isequal(expected_id,found_id); + if iscellstr(expected_id) + % recursive call + tf=any(cellfun(@(x)exception_id_matches(x,found_id),expected_id)); + return; + end + + tf=is_wildcard_id(expected_id) ... + || isequal(expected_id,found_id); diff --git a/extern/MOxUnit/MOxUnit/assertFalse.m b/extern/MOxUnit/MOxUnit/assertFalse.m index 3d854376606..66102e6c0d0 100644 --- a/extern/MOxUnit/MOxUnit/assertFalse.m +++ b/extern/MOxUnit/MOxUnit/assertFalse.m @@ -11,6 +11,16 @@ function assertFalse(a, message) % 'MOxUnit:notLogicalScalar' a is not a logical scalar % 'MOxUnit:notFalse' a is not false % +% Examples: +% assertFalse(false); +% %|| % ok +% +% assertFalse(true); +% %|| error('input does not evaluate to false'); +% +% assertFalse([false,false]); +% %|| error('input is not a logical scalar'); +% % Notes: % - If a custom message is provided, then any error message is prefixed % by this custom message @@ -42,7 +52,7 @@ function assertFalse(a, message) full_message=moxunit_util_input2str(message,whatswrong,a); if moxunit_util_platform_is_octave() - error(error_id,full_message); + error(error_id,'%s',full_message); else - throwAsCaller(MException(error_id, full_message)); + throwAsCaller(MException(error_id,'%s',full_message)); end diff --git a/extern/MOxUnit/MOxUnit/assertGreaterThan.m b/extern/MOxUnit/MOxUnit/assertGreaterThan.m new file mode 100644 index 00000000000..cb0087463c0 --- /dev/null +++ b/extern/MOxUnit/MOxUnit/assertGreaterThan.m @@ -0,0 +1,59 @@ +function assertGreaterThan(a, b, message) +% assert that a is greater than b +% +% assertGreaterThan(a,b,[msg]) +% +% Inputs: +% a first input } of any +% b second input } type +% msg optional custom message +% +% Raises: +% 'assertGreaterThan:sizeNotEqual' a and b are of different size +% 'assertGreaterThan:classNotEqual' a and b are of different class +% 'assertGreaterThan:notGreaterThan' values in a must be greater +% than values in b +% +% Examples: +% assertGreaterThan(2,1); +% %|| % passes without output +% +% assertGreaterThan(1,2); +% %|| error('first input argument in not larger than the second'); +% +% assertGreaterThan([2 3],[0;1]); +% %|| error('inputs are not of the same size'); + + if ~isequal(size(a), size(b)) + whatswrong='inputs are not of the same size'; + error_id='assertGreaterThan:sizeNotEqual'; + + elseif ~isequal(class(a), class(b)) + whatswrong='inputs are not of the same class'; + error_id='assertGreaterThan:classNotEqual'; + + elseif length(a) == 1 && length(b) == 1 && ~(a > b) + whatswrong='first input argument in not larger than the second'; + error_id='assertGreaterThan:notGreaterThan'; + + elseif ~all(a > b) + whatswrong=['each element in the first input must be greater than ',... + 'the corresponding element in the second input']; + error_id='assertGreaterThan:notGreaterThan'; + + else + % assertion passed + return; + end + + if nargin<3 + message=''; + end + + full_message=moxunit_util_input2str(message,whatswrong,a,b); + + if moxunit_util_platform_is_octave() + error(error_id,'%s',full_message); + else + throwAsCaller(MException(error_id, '%s', full_message)); + end \ No newline at end of file diff --git a/extern/MOxUnit/MOxUnit/assertLessThan.m b/extern/MOxUnit/MOxUnit/assertLessThan.m new file mode 100644 index 00000000000..50942a9b19a --- /dev/null +++ b/extern/MOxUnit/MOxUnit/assertLessThan.m @@ -0,0 +1,59 @@ +function assertLessThan(a, b, message) +% assert that a is less than b +% +% assertLessThan(a,b,[msg]) +% +% Inputs: +% a first input } of any +% b second input } type +% msg optional custom message +% +% Raises: +% 'assertLessThan:sizeNotEqual' a and b are of different size +% 'assertLessThan:classNotEqual' a and b are of different class +% 'assertLessThan:notLessThan' values in a must be less +% than values in b +% +% Examples: +% assertLessThan(1,2); +% %|| % passes without output +% +% assertLessThan(2,1); +% %|| error('first input argument in not smaller than the second'); +% +% assertLessThan([0 1],[2;3]); +% %|| error('inputs are not of the same size'); + + if ~isequal(size(a), size(b)) + whatswrong='inputs are not of the same size'; + error_id='assertLessThan:sizeNotEqual'; + + elseif ~isequal(class(a), class(b)) + whatswrong='inputs are not of the same class'; + error_id='assertLessThan:classNotEqual'; + + elseif length(a) == 1 && length(b) == 1 && ~(a < b) + whatswrong='first input argument in not smaller than the second'; + error_id='assertLessThan:notLessThan'; + + elseif ~all(a < b) + whatswrong=['each element in the first input must be less than ',... + 'the corresponding element in the second input']; + error_id='assertLessThan:notLessThan'; + + else + % assertion passed + return; + end + + if nargin<3 + message=''; + end + + full_message=moxunit_util_input2str(message,whatswrong,a,b); + + if moxunit_util_platform_is_octave() + error(error_id,'%s',full_message); + else + throwAsCaller(MException(error_id, '%s', full_message)); + end \ No newline at end of file diff --git a/extern/MOxUnit/MOxUnit/assertNotEqual.m b/extern/MOxUnit/MOxUnit/assertNotEqual.m new file mode 100644 index 00000000000..68c39422174 --- /dev/null +++ b/extern/MOxUnit/MOxUnit/assertNotEqual.m @@ -0,0 +1,72 @@ +function assertNotEqual(a, b, message) +% assert that two inputs are not equal +% +% assertNotEqual(a,b,[msg]) +% +% Inputs: +% a first input } of any +% b second input } type +% msg optional custom message +% +% Raises: +% 'assertNotEqual:equal' a and b are equal, and are either +% both sparse or both not sparse +% +% Examples: +% assertNotEqual('foo','bar'); +% %|| % passes without output +% +% assertNotEqual(1,sparse(1)); +% %|| % passes without output +% +% assertNotEqual('foo','foo'); +% %|| error('elements are equal'); +% +% Notes: +% - If a custom message is provided, then any error message is prefixed +% by this custom message +% - In this function, NaN values are considered equal. +% - This function attempts to show similar behaviour as in +% Steve Eddins' MATLAB xUnit Test Framework (2009-2012) +% URL: http://www.mathworks.com/matlabcentral/fileexchange/ +% 22846-matlab-xunit-test-framework +% + + % Note: although it may seem more logical to compare class before size, + % for compatibility reasons the order of tests matches that of the + % MATLAB xUnit framework + + if isequaln_wrapper(a,b) && issparse(a)==issparse(b) + whatswrong='inputs are equal'; + error_id='assertNotEqual:equal'; + + else + % elements are equal + return; + end + + if nargin<3 + message=''; + end + + full_message=moxunit_util_input2str(message,whatswrong,a,b); + + if moxunit_util_platform_is_octave() + error(error_id,'%s',full_message); + else + throwAsCaller(MException(error_id, '%s', full_message)); + end + +function tf=isequaln_wrapper(a,b) +% wrapper to support old versions of Matlab + persistent has_equaln; + + if isempty(has_equaln) + has_equaln=~isempty(which('isequaln')); + end + + if has_equaln + tf=isequaln(a,b); + else + tf=isequalwithequalnans(a,b); + end diff --git a/extern/MOxUnit/MOxUnit/assertTrue.m b/extern/MOxUnit/MOxUnit/assertTrue.m index 79397267592..853dfc64499 100644 --- a/extern/MOxUnit/MOxUnit/assertTrue.m +++ b/extern/MOxUnit/MOxUnit/assertTrue.m @@ -11,6 +11,17 @@ function assertTrue(a, message) % 'MOxUnit:notLogicalScalar' a is not a logical scalar % 'MOxUnit:notTrue' a is not true % +% Examples: +% assertTrue(true); +% %|| % ok +% +% assertTrue(false); +% +% %|| error('input does not evaluate to true'); +% +% assertFalse([true,true]); +% %|| error('input is not a logical scalar'); +% % Notes: % - If a custom message is provided, then any error message is prefixed % by this custom message @@ -42,7 +53,7 @@ function assertTrue(a, message) full_message=moxunit_util_input2str(message,whatswrong,a); if moxunit_util_platform_is_octave() - error(error_id,full_message); + error(error_id,'%s',full_message); else - throwAsCaller(MException(error_id, full_message)); + throwAsCaller(MException(error_id,'%s',full_message)); end diff --git a/extern/MOxUnit/MOxUnit/assertVectorsAlmostEqual.m b/extern/MOxUnit/MOxUnit/assertVectorsAlmostEqual.m index 47654408fee..cb57c38128d 100644 --- a/extern/MOxUnit/MOxUnit/assertVectorsAlmostEqual.m +++ b/extern/MOxUnit/MOxUnit/assertVectorsAlmostEqual.m @@ -21,6 +21,22 @@ function assertVectorsAlmostEqual(a,b,varargin) % 'moxunit:floatsDiffer' values in a and b are not % almost equal (see note below) % +% Examples: +% assertVectorsAlmostEqual([1 2 3 4],[1 2 3 4]); +% %|| % ok +% +% assertVectorsAlmostEqual([1 2; 3 4],[1 2; 3 4]); +% %|| error('first input is not a vector') +% +% assertVectorsAlmostEqual(1,1.0001); +% %|| error('inputs are not equal within relative tolerance 1.49011e-08') +% +% assertVectorsAlmostEqual(1,1.0001,'absolute',1e-3); +% %|| % no error +% +% assertVectorsAlmostEqual('foo','bar') +% %|| error('first input is not float'); +% % % Notes: % - If tol_type is 'relative', a and b are almost equal if @@ -60,8 +76,8 @@ function assertVectorsAlmostEqual(a,b,varargin) full_message=moxunit_util_input2str(message,whatswrong,a,b); if moxunit_util_platform_is_octave() - error(error_id,full_message); + error(error_id,'%s',full_message); else - throwAsCaller(MException(error_id, full_message)); + throwAsCaller(MException(error_id,'%s',full_message)); end diff --git a/extern/MOxUnit/MOxUnit/assertWarning.m b/extern/MOxUnit/MOxUnit/assertWarning.m new file mode 100644 index 00000000000..074dfd27680 --- /dev/null +++ b/extern/MOxUnit/MOxUnit/assertWarning.m @@ -0,0 +1,214 @@ +function assertWarning(func, expected_id, message) +% Assert that an warning is thrown +% +% assertWarning(func, [expected_id,[message]]) +% +% Inputs: +% func Function handle that is expected to throw, with the +% prototype +% [varargout{:}] = func() +% expected_id Identifier of the expected warning (optional). If +% not provided, then func can raise any warning. +% To allow for any warning to be raised, use '*'. +% Multiple ids can be provided in a cell string; the +% assertion means that at least one id matches the one +% that is thrown. +% message Custom message to be included when func fails to throw +% +% Throws: +% 'moxunit:warningNotRaised' func() does not raise an warning but +% was expected to do so. +% 'moxunit:wrongWarningRaised' func() does raise an warning but with +% an identifier different from +% expected_id +% 'moxunit:illegalParameter' the input arguments are of the wrong +% type or content +% +% +% Examples: +% % Assert that solving with a singular matrix give a warning +% assertWarning( @() zeros(2) \ ones(2,1) ); +% %|| % ok +% +% % Assert that solving with a singular matrix give a warning +% % AND that the ID is either 'Octave:singular-matrix' or +% % 'MATLAB:singularMatrix' +% allowed_ids={'Octave:singular-matrix','MATLAB:singularMatrix'}; +% assertWarning( @() zeros(2) \ ones(2,1), allowed_ids); +% +% % No warning raised +% assertWarning(@()disp('hello world')); +% %|| error('No warning was raised'); +% +% Notes: +% - This function allows one to test for warnings being thrown, and +% optionally, pass a custome message in response to a failure. +% - It is assumed that all input variables are of the correct type, valid +% (if applicable), and given in the correct order. +% +% See also: moxunit_util_is_message_identifier + + if nargin<3 + message=''; + end + + if nargin<2 + expected_id='*'; + end + + verify_inputs(func, expected_id, message); + + state = warning('query'); + try + % In octave, silencing warnings does not allow us to + % get them through lastwarn + warning('error', 'all'); + catch + % In matlab, setting all warnings to errors is not allowed + warning('off', 'all'); + if ~is_wildcard_id(expected_id) + if ~iscellstr(expected_id) + expected_id = {expected_id}; + end + for i=expected_id + if ~isempty(i{:}) + warning('error', i{:}); + end + end + end + end + + lastwarn('', ''); + + try + func(); + + [message,warning_id] = lastwarn(); + + if ~isempty(message) || ~isempty(warning_id) + if ~isempty(warning_id) + error(warning_id, message); + else + error(message); + end + end + + [id,whats_wrong]=warning_not_raised(expected_id); + catch + % (Avoiding '~' for Octave compatibility) + [unused,found_id] = lasterr(); + + if warning_id_matches(expected_id,found_id) + % the expected warning was raised, we're done + for i=1:numel(state) + warning(state(i).state, state(i).identifier); + end + return; + end + + [id,whats_wrong]=wrong_warning_raised(found_id, expected_id); + end + + for i=1:numel(state) + warning(state(i).state, state(i).identifier); + end + + full_message = moxunit_util_input2str(message, whats_wrong); + + if moxunit_util_platform_is_octave() + error(id,'%s',full_message); + else + throwAsCaller(MException(id,'%s',full_message)); + end + + +function [id,whats_wrong]=warning_not_raised(expected_id) + id = 'moxunit:warningNotRaised'; + whats_wrong='No warning was raised'; + + if ~any(strcmp(expected_id,'*')) + % add suffix with expected id + whats_wrong=sprintf('%s, expected warning %s',... + whats_wrong,expected_id2str(expected_id)); + end + + +function [id,whatswrong]=wrong_warning_raised(found_id, expected_id) + last_err=lasterror(); + stack=last_err.stack; + + stack_indent_length=8; + stack_indent=repmat(' ',1,stack_indent_length); + %stack_indent=' | ' + stack_trace_str=moxunit_util_stack2str(stack,stack_indent); + + id='moxunit:wrongWarningRaised'; + whatswrong = sprintf(... + ['warning ''%s'' was raised, expected %s. '... + 'Stack trace:\n\n%s'],... + found_id, expected_id2str(expected_id), stack_trace_str); + +function expected_str=expected_id2str(expected_id) + if iscellstr(expected_id) && numel(expected_id)>1 + expected_str=sprintf('one of ''%s''',... + moxunit_util_strjoin(expected_id,''', ''')); + else + if iscellstr(expected_id) + expected_id=expected_id{1}; + end + expected_str=sprintf('''%s''',expected_id); + end + + +function verify_inputs(varargin) + expected_type_funcs={@(x)isa(x,'function_handle'),... + @(x)ischar(x) || iscellstr(x),... + @ischar,... + }; + for k=1:numel(expected_type_funcs) + expected_type_func=expected_type_funcs{k}; + arg=varargin{k}; + if ~expected_type_func(arg) + error('moxunit:illegalParameter',... + ['Argument at position %d of wrong type, '... + 'found ''%s'''],... + k,class(varargin{k})); + end + end + + expected_id=varargin{2}; + if ~is_valid_error_id(expected_id) + error('moxunit:illegalParameter',... + ['Argument at position 2, ''expected_id'', '... + 'must be a cellstring or single string; each '... + 'string must be empty, an asterisk (''*''), or '... + 'a valid message identifier, found ''%s'''],... + expected_id); + end + +function tf=is_valid_error_id(expected_id) + if iscellstr(expected_id) + % recursive call + tf=all(cellfun(@is_valid_error_id,expected_id)); + return + end + + tf=strcmp(expected_id,'') ... + || strcmp(expected_id,'*') ... + || iscellstr(expected_id) ... + || moxunit_util_is_message_identifier(expected_id); + + + +function tf=is_wildcard_id(id) + tf=any(strcmp(id,'*')); + +function tf=warning_id_matches(expected_id,found_id) + if iscellstr(expected_id) + % recursive call + tf=any(cellfun(@(x)warning_id_matches(x,found_id),expected_id)); + return; + end + + tf=is_wildcard_id(expected_id) ... + || isequal(expected_id,found_id); diff --git a/extern/MOxUnit/MOxUnit/initTestSuite.m b/extern/MOxUnit/MOxUnit/initTestSuite.m index 82321280ca9..a1d9030efa3 100644 --- a/extern/MOxUnit/MOxUnit/initTestSuite.m +++ b/extern/MOxUnit/MOxUnit/initTestSuite.m @@ -7,14 +7,18 @@ % use the following layout: % % function test_suite=my_test_of_abs -% initTestSuite +% try % assignment of 'localfunctions' is necessary in Matlab >= 2016 +% test_functions=localfunctions(); +% catch % no problem; early Matlab versions can use initTestSuite fine +% end +% initTestSuite; % % function test_abs_scalar % assertTrue(abs(-1)==1) % assertEqual(abs(-NaN),NaN); % assertEqual(abs(-Inf),Inf); % assertEqual(abs(0),0) -% assertElementsAlmostEqual(abs(-1e-13),0) +% assertElementsAlmostEqual(abs(-1e-13),0) % % function test_abs_vector % assertEqual(abs([-1 1 -3]),[1 1 3]); @@ -38,40 +42,119 @@ % % NNO Jan 2014 + get_example_syntax=@()sprintf('%s\n',... + '',... + ' function test_suite=test_foo',... + ' % tests for function ''foo''',... + [' try % assignment of '... + '''localfunctions'' is necessary '... + 'in Matlab >= 2016'],... + [' test_functions='... + 'localfunctions();'],... + [' catch % no problem; early Matlab '... + 'versions can use '... + 'initTestSuite fine'],... + ' end',... + ' initTestSuite;',... + '',... + ' function test_foo1',... + ' % your test code here',... + '',... + ' function test_foo2',... + ' % your test code here'... + ); + + e=lasterror(); [call_stack, idx] = dbstack('-completenames'); + if numel(call_stack)==1 + example_syntax=get_example_syntax(); + + error(['The script ''%s'' must be called from within '... + 'another function, typically using the '... + 'following syntax:\n\n%s'... + ],... + call_stack(1).name,... + example_syntax); + end caller_fn=call_stack(idx+1).file; + sub_func_struct=moxunit_util_mfile_subfunctions(caller_fn); + + has_error=false; - subfunction_names=moxunit_util_mfile_subfunctions(caller_fn); + if moxunit_util_platform_supports('localfunctions_in_script') + n_sub_func=numel(sub_func_struct); - n=numel(subfunction_names); - tests=cell(n,1); + handle_candidates=cell(n_sub_func,1); + for k=1:n_sub_func + handle_candidates{k}=str2func(sub_func_struct(k).name); + end + + else + if ~exist('test_functions','var') + func_name=call_stack(2).name; + example_syntax=get_example_syntax(); + + func_error=@()error(... + ['In %s:\nThe variable ''test_functions'' is '... + 'not assigned and this version\nof Matlab does '... + 'not support the assignment of function handles\n'... + 'through calls by functions. \nAdapt the function '... + '%s so that it resembles the following '... + 'syntax:\n%s'],... + func_name, func_name, example_syntax); + handle_candidates={func_error}; + has_error=true; + else + handle_candidates=test_functions; + end + end + test_re=moxunit_util_get_test_name_regexp(); + + n_sub_func=numel(sub_func_struct); + sub_func_names=arrayfun(@(i)sub_func_struct(i).name,1:n_sub_func,... + 'UniformOutput',false); + + n_handles=numel(handle_candidates); test_suite=MOxUnitTestSuite(); - for k=1:n - subfunction_name=subfunction_names{k}; - if moxunit_util_is_test_function_name(subfunction_name) - try - func=str2func(subfunction_name); - if nargout(func)==0 - test_case=MOxUnitFunctionHandleTestCase(... - subfunction_name,... - caller_fn, func); - test_suite=addTest(test_suite, test_case); + for k=1:n_handles + handle=handle_candidates{k}; + + add_test=false; + + if has_error + name=call_stack(1).name; + add_test=true; + else + name=func2str(handle); + if moxunit_util_regexp_matches(name,test_re) + idx=find(strcmp(name,sub_func_names)); + if ~isempty(idx) + assert(numel(idx)==1, 'name match should be unique'); + add_test=sub_func_struct(idx).nargout==0; end - catch end end + + if add_test + test_case=MOxUnitFunctionHandleTestCase(... + name,... + caller_fn, handle); + test_suite=addTest(test_suite, test_case); + end + end - % If the user didn't request an output, immediately execute the test and - % display its result - if ~nargout + % If not called from another function, execute the test directly + % and remove the test_suite variable + if numel(call_stack)==2 disp(run(test_suite)); % Avoid showing "ans = MOxUnitTestSuite object: 1-by-1" % when run without explicitly assigning output to a variable clear test_suite; - end \ No newline at end of file + end + diff --git a/extern/MOxUnit/MOxUnit/moxunit_runtests.m b/extern/MOxUnit/MOxUnit/moxunit_runtests.m index 9ed8a92bd7c..ca9b74f0440 100644 --- a/extern/MOxUnit/MOxUnit/moxunit_runtests.m +++ b/extern/MOxUnit/MOxUnit/moxunit_runtests.m @@ -9,11 +9,13 @@ % '-quiet' do not show output % filename } test the unit tests in filename % directory } (which must initialize a test suite through -% } initTestSuite) or in directory. Multiple -% filename or directory arguments can be +% suite } initTestSuite), in the directory, or the +% } MOxUnitTestSuite instance. +% Multiple filename or directory arguments can be % provided. If there are no filename or directory % arguments, then all tests in the current % directory are run. +% % '-recursive' If this option is present, then files are added % recursively from any directory. If absent, then % only files from each directory (but not their @@ -70,21 +72,22 @@ params=get_params(varargin{:}); if params.fid>2 - % not standard or error output; file most be closed + % not standard or error output; file must be closed % afterwards cleaner=onCleanup(@()fclose(params.fid)); end suite=MOxUnitTestSuite(); - for k=1:numel(params.filenames) - % add files to the test suite - filename=params.filenames{k}; - if isdir(filename) - suite=addFromDirectory(suite,filename,[],params.add_recursive); - else - suite=addFromFile(suite,filename); - end - end + + % build pattern for filenames by combining the test name pattern with + % extension + mfile_ext_pattern='.m$'; + mfile_test_filename_pattern=get_test_file_pattern(mfile_ext_pattern); + + suite=add_from_to_test_spec(suite, ... + params.to_test_spec,... + mfile_test_filename_pattern,... + params.add_recursive); % show summary of test suite if params.verbosity>0 @@ -102,7 +105,7 @@ disp(test_report); if ~isempty(params.junit_xml_file) - write_junit_xml(params.junit_xml_file, test_report); + writeXML(test_report,params.junit_xml_file); end @@ -110,6 +113,38 @@ result=wasSuccessful(test_report); +function mfile_test_filename_pattern=get_test_file_pattern(... + mfile_ext_pattern) + test_name_pattern=moxunit_util_get_test_name_regexp(); + assert(sum(test_name_pattern=='$')==1,'unexpected pattern'); + assert(test_name_pattern(end)=='$','unexpected pattern'); + + mfile_test_filename_pattern=regexprep(test_name_pattern,... + '\$',... + mfile_ext_pattern); + + +function suite=add_from_to_test_spec(suite, ... + to_test_spec, ... + mfile_test_filename_pattern,... + add_recursive) + for k=1:numel(to_test_spec) + % add files to the test suite + to_test=to_test_spec{k}; + if isa(to_test,'MOxUnitTestSuite') + suite=addFromSuite(suite,to_test); + elseif moxunit_util_isfolder(to_test) + suite=addFromDirectory(suite,... + to_test,... + mfile_test_filename_pattern,... + add_recursive); + else + suite=addFromFile(suite,to_test); + end + end + + + function test_report=run_all_tests(suite, test_report, params) f_handle=@()run(suite, test_report,... params.partition_index,params.partition_count); @@ -141,7 +176,7 @@ elseif iscell(value) n_values=numel(value); param_elem_matrix=[repmat({key_arg},1,n_values);... - value(:)]; + value(:)']; param_elem=param_elem_matrix(:)'; else error('moxunit:illegalParameterValue',... @@ -157,28 +192,27 @@ all_params=[mocov_expr_param, mocov_params]; + if params.verbosity>=1 + params_as_strings=cellfun(@param2str,all_params,... + 'UniformOutput',false); + params_joined=sprintf('%s ',params_as_strings{:}); + + fprintf('running coverage with parameters: %s\n',... + params_joined); + end + + test_report=mocov(all_params{:}); else test_report=f_handle(); end - - - -function write_junit_xml(fn, test_report) - fid=fopen(fn,'w'); - file_closer=onCleanup(@()fclose(fid)); - - xml_preamble=''; - xml_header=''; - xml_body=getSummaryStr(test_report,'xml'); - xml_footer=''; - - fprintf(fid,'%s\n%s\n%s\n%s',... - xml_preamble,... - xml_header,... - xml_body,... - xml_footer); +function s=param2str(p) + if ischar(p) + s=p; + else + s=sprintf('<%s>',class(p)); + end function params=get_params(varargin) @@ -200,16 +234,21 @@ function write_junit_xml(fn, test_report) % allocate space for filenames n=numel(varargin); - filenames=cell(n,1); + to_test_spec=cell(n,1); k=0; while k error('moxunit:illegalParameter',... 'Parameter not recognised or file missing: %s', arg); end end end - filenames=filenames(~cellfun(@isempty,filenames)); + to_test_spec=to_test_spec(~cellfun(@isempty,to_test_spec)); - if numel(filenames)==0 + if numel(to_test_spec)==0 me_name=mfilename(); if isequal(pwd(), fileparts(which(me_name))) error('moxunit:illegalParameter',... @@ -341,13 +344,28 @@ function write_junit_xml(fn, test_report) 'on MOxUnit itself, use:\n\n'... ' %s ../tests'], me_name, me_name); end - filenames={pwd()}; + to_test_spec={pwd()}; end - params.filenames=filenames; + params.to_test_spec=to_test_spec; check_cover_consistency(params) + +function params = set_key_value(params,args,k) + n=numel(args); + + arg = args{k}; + assert(arg(1)=='-'); + key=arg(2:end); + + if k==n + error('moxunit:missingParameter',... + 'Missing parameter after option ''%s''',arg); + end + + params.(key)=args{k+1}; + function check_cover_consistency(params) keys=fieldnames(params); diff --git a/extern/MOxUnit/MOxUnit/util/moxunit_isa_test_skipped_exception.m b/extern/MOxUnit/MOxUnit/util/moxunit_isa_test_skipped_exception.m index abc446a4355..da3b600c041 100644 --- a/extern/MOxUnit/MOxUnit/util/moxunit_isa_test_skipped_exception.m +++ b/extern/MOxUnit/MOxUnit/util/moxunit_isa_test_skipped_exception.m @@ -21,18 +21,11 @@ persistent cached_test_skipped_identifier if isempty(cached_test_skipped_identifier) - if moxunit_util_platform_is_octave() - try - moxunit_throw_test_skipped_exception('error'); - catch - last_caught_error=lasterror(); - end - - else - try - moxunit_throw_test_skipped_exception('error'); - catch last_caught_error; - end + try + moxunit_throw_test_skipped_exception('error'); + assert(false,'should never get here'); + catch + last_caught_error=lasterror(); end cached_test_skipped_identifier=last_caught_error(1).identifier; diff --git a/extern/MOxUnit/MOxUnit/util/moxunit_util_elem2str.m b/extern/MOxUnit/MOxUnit/util/moxunit_util_elem2str.m index 01c92488666..8e0b44bd666 100644 --- a/extern/MOxUnit/MOxUnit/util/moxunit_util_elem2str.m +++ b/extern/MOxUnit/MOxUnit/util/moxunit_util_elem2str.m @@ -24,21 +24,30 @@ max_chars=100; end + body_str=''; + try if is_row_vector_string(elem) - elem_str=['''' elem '''']; + body_str=['''' elem '''']; elseif numel(elem)<=max_chars % try pretty printing - elem_str=tiny_elem2str(elem); - else - elem_str=elem2str_size_and_class(elem); + body_str=tiny_elem2str(elem); end + + body_str=limit_string_size(body_str,max_chars); catch - % fall back - elem_str=elem2str_class(elem); + % use empty body string end - elem_str=limit_string_size(elem_str,max_chars); + header_str=get_header_str(elem); + if isempty(body_str) + suffix=''; + else + suffix=sprintf('\n%s',body_str); + end + + elem_str=sprintf('%s%s',header_str,suffix); + function elem_str=tiny_elem2str(elem) @@ -63,9 +72,10 @@ elseif isnumeric(elem) % If the element is numeric, we can use mat2str - elem_str=mat2str(elem); + precision=5; + elem_str=mat2str(elem,precision); - elseif exist('evalc', 'builtin') + elseif can_use_evalc() % If we have `evalc`, we can just trust the `display` function % provided by the environment to format the string correctly % and capture it with `evalc`. @@ -74,10 +84,21 @@ % remove trailing newlines elem_str=regexprep(elem_str,sprintf('[\n]*$'),''); + % remove 'ans = ' from the beginning, if running octave + if moxunit_util_platform_is_octave() + + elem_str=regexprep(elem_str,'^ans\s*= ',''); + elem_str=regexprep(elem_str,'^ans\(:,:,',sprintf('\n(:,:,')); + elem_str=regexprep(elem_str,sprintf('\nans\\(:,:,'),... + sprintf('\n(:,:,')); + end + + + else % If evalc is not present (Octave), just show the size and % the class - elem_str=elem2str_size_and_class(elem); + elem_str=''; end @@ -95,18 +116,38 @@ end +function header_str=get_header_str(elem) +% if possible, return size and class. If this raises an exception, return +% the class only + try + header_str=elem2str_size_and_class(elem); + catch + header_str=elem2str_class(elem); + end + + function elem_str=elem2str_size_and_class(elem) % return string representation of elem with size and class - siz_cell = arrayfun(@num2str, size(elem), ... + siz = size(elem); + siz_cell = arrayfun(@num2str, siz, ... 'UniformOutput', false); siz_str = moxunit_util_strjoin(siz_cell, 'x'); - elem_str = sprintf('%s(%s)', siz_str, class(elem)); + class_str = elem2str_class(elem); + + if any(siz==0) + suffix=' (empty)'; + else + suffix=''; + end + + elem_str = sprintf('%s %s%s', siz_str, class_str, suffix); -function elem_str=elem2str_class(elem) + +function class_str=elem2str_class(elem) % return string representation of elem with class only; used as a fallback % option if tiny_elem2str and elem2str_size_and_class cannot be used - elem_str = sprintf('(%s)', class(elem)); + class_str = sprintf('%s', class(elem)); function elem_str=limit_string_size(elem_str, max_chars) @@ -117,4 +158,20 @@ elem_str=[elem_str(1:n) infix elem_str(end+((1-n):0))]; end +function tf=can_use_evalc() + tf=false; + + code_is_mfile=2; + code_is_mex_or_oct_file=3; + func_name='evalc'; + if exist(func_name,'builtin') + tf=true; + return; + end + + exist_code=exist(func_name); + if any(exist_code==[code_is_mfile,code_is_mex_or_oct_file]) + tf=true; + return; + end diff --git a/extern/MOxUnit/MOxUnit/util/moxunit_util_find_files.m b/extern/MOxUnit/MOxUnit/util/moxunit_util_find_files.m new file mode 100644 index 00000000000..dd7c9883b83 --- /dev/null +++ b/extern/MOxUnit/MOxUnit/util/moxunit_util_find_files.m @@ -0,0 +1,69 @@ +function result=moxunit_util_find_files(root_dir,re,add_recursive) +% Find files matching a pattern +% +% result=moxunit_util_find_files(root_dir,re,add_recursive) +% +% Inputs: +% root_dir Root directory in which to look for files. +% re Regular expression of file names to match +% add_recursive (optional) If true, then files are added recursively +% from sub-directories of root_dir (and from sub-sub-, +% sub-sub-sub-,... -directories). If false then only +% files in root_dir, but not in its subdirectories, are +% considered. Default: true +% +% Output: +% result Cell with matching files in directory +% +% NNO 2015 + + if ~moxunit_util_isfolder(root_dir) + error('Diectory input argument must be a directory'); + end + + if ~ischar(re) + error('Second argument must be a string'); + end + + if nargin<3 || isempty(add_recursive) + add_recursive=true; + end + + result=find_files_helper(root_dir,re,add_recursive); + + +function result=find_files_helper(root_dir,re,add_recursive) + if moxunit_util_isfolder(root_dir) + d=dir(root_dir); + n=numel(d); + + result_cell=cell(n,1); + + for k=1:n + fn=d(k).name; + + if ignore_directory(fn) + continue; + end + + path_fn=fullfile(root_dir,fn); + + if moxunit_util_isfolder(path_fn) + if add_recursive + result_cell{k}=find_files_helper(path_fn,re,... + add_recursive); + end + + elseif moxunit_util_regexp_matches(fn,re) + result_cell{k}={path_fn}; + end + end + + keep=~cellfun(@isempty,result_cell); + result_cell=result_cell(keep); + + result=cat(1,result_cell{:}); + end + +function tf=ignore_directory(fn) + tf=any(strcmp(fn,{'.','..'})); diff --git a/extern/MOxUnit/MOxUnit/util/moxunit_util_floats_almost_equal.m b/extern/MOxUnit/MOxUnit/util/moxunit_util_floats_almost_equal.m index d653935aa87..22bea201a2c 100644 --- a/extern/MOxUnit/MOxUnit/util/moxunit_util_floats_almost_equal.m +++ b/extern/MOxUnit/MOxUnit/util/moxunit_util_floats_almost_equal.m @@ -60,7 +60,7 @@ elseif ~isfloat(a) whatswrong='first input is not float'; error_id=get_error_id(f, 'notFloat'); - elseif ~isnumeric(b) + elseif ~isfloat(b) whatswrong='second input is not float'; error_id=get_error_id(f, 'notFloat'); else diff --git a/extern/MOxUnit/MOxUnit/util/moxunit_util_get_test_name_regexp.m b/extern/MOxUnit/MOxUnit/util/moxunit_util_get_test_name_regexp.m new file mode 100644 index 00000000000..dc165bb039d --- /dev/null +++ b/extern/MOxUnit/MOxUnit/util/moxunit_util_get_test_name_regexp.m @@ -0,0 +1,12 @@ +function re=moxunit_util_get_test_name_regexp() +% return regular expression indicating a test name or function +% +% re=moxunit_util_get_test_name_regexp() +% +% Output: +% re regular expression that matches a string starting with 'test' +% or ending with 'test' (case-insensitive) + + test_re='[Tt][Ee][sS][tT]'; + re=sprintf('^((%s.*)|(.*%s))$',test_re,test_re); + diff --git a/extern/MOxUnit/MOxUnit/util/moxunit_util_input2str.m b/extern/MOxUnit/MOxUnit/util/moxunit_util_input2str.m index 31734cc65f3..d02d1bddfd5 100644 --- a/extern/MOxUnit/MOxUnit/util/moxunit_util_input2str.m +++ b/extern/MOxUnit/MOxUnit/util/moxunit_util_input2str.m @@ -35,18 +35,12 @@ elseif nargin<4 % one element - string=sprintf('%s\n\nInput:%s\n', ... + string=sprintf('%s\n\nInput: %s\n', ... prefix,moxunit_util_elem2str(a)); else % two elements - string=sprintf('%s\n\nFirst input:\n%s\n\nSecond input:\n%s\n',... + string=sprintf('%s\n\nFirst input: %s\n\nSecond input: %s\n',... prefix, moxunit_util_elem2str(a),... moxunit_util_elem2str(b)); end - - string=strrep(string,sprintf('\n'),'\n'); - - - - diff --git a/extern/MOxUnit/MOxUnit/util/moxunit_util_isfolder.m b/extern/MOxUnit/MOxUnit/util/moxunit_util_isfolder.m new file mode 100644 index 00000000000..59e8cbbf864 --- /dev/null +++ b/extern/MOxUnit/MOxUnit/util/moxunit_util_isfolder.m @@ -0,0 +1,38 @@ +function tf=moxunit_util_isfolder(folderName) +% return true if the argument is a folder +% +% This function is needed to keep compatibility with older versions of Octave and Matlab +% for which the function isfolder didn't exist yet. +% +% tf=moxunit_util_isfolder(folderName) +% +% Output: +% tf True if the argument points to an existing directory +% + + + persistent cached_handle; + + if ~isempty(cached_handle) + tf = cached_handle(folderName); + return; + end + + % We find out which function we need to redirect to + v = moxunit_util_platform_version(); + isfolder_available = false; + if moxunit_util_platform_is_octave() + isfolder_available = (v(1) >= 5); + else + isfolder_available = (v(1) >= 10) || ((v(1) >= 9) && (v(2) >= 3)); + end + + % Call the appropriate function + if isfolder_available + cached_handle = @isfolder; + else + cached_handle = @isdir; + end + + tf = cached_handle(folderName); + diff --git a/extern/MOxUnit/MOxUnit/util/moxunit_util_mfile_subfunctions.m b/extern/MOxUnit/MOxUnit/util/moxunit_util_mfile_subfunctions.m index a9fa18d10f1..147ae620f23 100644 --- a/extern/MOxUnit/MOxUnit/util/moxunit_util_mfile_subfunctions.m +++ b/extern/MOxUnit/MOxUnit/util/moxunit_util_mfile_subfunctions.m @@ -1,4 +1,4 @@ -function names=moxunit_util_mfile_subfunctions(fn) +function fs=moxunit_util_mfile_subfunctions(fn) % find the names of subfunctions defined in an .m file % % names=moxunit_util_mfile_subfunctions(fn) @@ -7,7 +7,10 @@ % fn filename of .m file % % Output: -% names 1xP cell with the names of subfunctions in fn. +% fs Px1 struct (with P the number of subfunctions) +% with fields: +% .name name of the function +% .nargout number of output elements % % Example: % Running this function on itself returns two subfunctions, @@ -40,6 +43,7 @@ % function outputs can be specifed in three ways, namely with zero, % one, or more than one output argout_pat='\s*((\s([\w~]+\s*=)?)|(\[[\w~,\s]*\]\s*=))\s*'; + argout_pat='\s*(?(\s([\w~]+\s*=)?)|(\[[\w~,\s]*\]\s*=))\s*'; % function name that we are after @@ -59,14 +63,31 @@ match=regexp([newline text newline],pat,'tokens'); % extract function names - all_func_names=cellfun(@(x)x{end-1},match,'UniformOutput',false); - - if numel(all_func_names)==0 - names=cell(0); - else - names=all_func_names(2:end); + n_func=numel(match)-1; + names=cell(n_func,1); + nargouts=cell(n_func,1); + + for k=1:n_func + m=match{k+1}; + + args=m{end-2}; + args=regexprep(args,'[\[,\]=]+',' '); + args=regexprep(args,'\s+',' '); + args=regexprep(args,'^[ ]+',''); + args=regexprep(args,'[ ]+$',''); + + if isempty(args) + arg_count=0; + else + arg_count=1+numel(regexp(args,' ')); + end + + names{k}=m{end-1}; + nargouts{k}=arg_count; end + fs=struct('name',names,'nargout',nargouts); + function s = read_file(fn) fid = fopen(fn); diff --git a/extern/MOxUnit/MOxUnit/util/moxunit_util_platform_supports.m b/extern/MOxUnit/MOxUnit/util/moxunit_util_platform_supports.m new file mode 100644 index 00000000000..81ed5efde69 --- /dev/null +++ b/extern/MOxUnit/MOxUnit/util/moxunit_util_platform_supports.m @@ -0,0 +1,42 @@ +function flag=moxunit_util_platform_supports(key) +% Return whether the current Matlab / octave supports certain functionality +% +% flag=moxunit_util_platform_supports(key) +% +% Inputs: +% key Functionality, must be one of: +% 'localfunctions_in_script' - whether scripts called from a +% function have access to local +% subfunctions in that function +% (Functionality removed in Matlab +% 2016a) +% 'diagnostics_recording_plugin' - whether the +% matlab.unittest.plugins +% .DiagnosticsRecordingPlugin +% is available +% +% Output: +% flag true or false, indicating whether the current platform supports +% the functionality indicated by the key argument + + if ~ischar(key) + error('Input must be a string'); + end + + switch key + case 'localfunctions_in_script' + if moxunit_util_platform_is_octave() + v=moxunit_util_platform_version(); + flag=v(1)<6; % before 6.0.0 + else + v=moxunit_util_platform_version(); + flag=v(1)<9; % before 2016a + end + + case 'diagnostics_recording_plugin' + s='matlab.unittest.plugins.DiagnosticsRecordingPlugin'; + flag=~isempty(which(s)); + + otherwise + error('Unsupported key %s', key); + end diff --git a/extern/MOxUnit/MOxUnit/util/moxunit_util_platform_version.m b/extern/MOxUnit/MOxUnit/util/moxunit_util_platform_version.m new file mode 100644 index 00000000000..8488811face --- /dev/null +++ b/extern/MOxUnit/MOxUnit/util/moxunit_util_platform_version.m @@ -0,0 +1,22 @@ +function v=moxunit_util_platform_version() +% return the version of Matlab or Octave +% +% v=moxunit_util_platform_version() +% +% Output: +% v Vector with two (Matlab) or three (Octave) elements, +% for example 8.5 (for Matlab) or 4.0.3) for Octave) +% + + + version_str=version(); + + % support also strings stuch as 6.1.1~hg.2021.01.26 + parts=regexp(version_str,'[^0-9.]','split'); + first_part=parts{1}; + + num_parts=regexp(first_part,'\.','split'); + v=cellfun(@str2num,num_parts); + + + diff --git a/extern/MOxUnit/MOxUnit/util/moxunit_util_regexp_matches.m b/extern/MOxUnit/MOxUnit/util/moxunit_util_regexp_matches.m new file mode 100644 index 00000000000..c69c8509715 --- /dev/null +++ b/extern/MOxUnit/MOxUnit/util/moxunit_util_regexp_matches.m @@ -0,0 +1,26 @@ +function tf=moxunit_util_regexp_matches(str, pat) +% return whether a string matches a regular pattern +% +% tf=moxunit_util_regexp_matches(str, pat) +% +% Inputs: +% str string to match +% pat regular rexpression to match +% +% Output: +% tf true if str matches the pattern pat, false +% otherwise +% + check_inputs(str, pat); + + tf=~isempty(regexp(str,pat,'once')); + + +function check_inputs(str, pat) + if ~ischar(str) + error('first argument must be a string'); + end + + if ~ischar(pat) + error('second argument must be a string'); + end diff --git a/extern/MOxUnit/MOxUnit/util/moxunit_util_stack2str.m b/extern/MOxUnit/MOxUnit/util/moxunit_util_stack2str.m new file mode 100644 index 00000000000..859d3dcaf7c --- /dev/null +++ b/extern/MOxUnit/MOxUnit/util/moxunit_util_stack2str.m @@ -0,0 +1,42 @@ +function s=moxunit_util_stack2str(stack,prefix) +% Return string representation of calling stack +% +% s=moxunit_util_stack2str(stack) +% +% Inputs: +% stack Nx1 struct with fields .name, .line and .file +% prefix (optional) prefix for each line; if omitted the +% empty string ('') is used. +% +% Output: +% s String representation of the stack, showing one +% element per line. The represention is without a +% trailing newline +% + if nargin<2 + prefix=''; + end + + check_input(stack); + n=numel(stack); + + line_pat='%s:%d (%s)'; + elem2str=@(x) [prefix, sprintf(line_pat,x.name,x.line,x.file)]; + + lines=arrayfun(@(i) elem2str(stack(i)),1:n,... + 'UniformOutput',false); + s=moxunit_util_strjoin(lines,sprintf('\n')); + + +function check_input(stack) + if ~isstruct(stack) + error('Input must be a struct'); + end + + required_keys={'file','name','line'}; + missing_keys=setdiff(required_keys,fieldnames(stack)); + + if ~isempty(missing_keys) + error('Missing key in stack: ''%s''',missing_keys{1}); + end + diff --git a/extern/MOxUnit/MOxUnit/util/moxunit_util_strjoin.m b/extern/MOxUnit/MOxUnit/util/moxunit_util_strjoin.m index cd9c9e6068a..0e83ee86592 100644 --- a/extern/MOxUnit/MOxUnit/util/moxunit_util_strjoin.m +++ b/extern/MOxUnit/MOxUnit/util/moxunit_util_strjoin.m @@ -23,15 +23,17 @@ % % Examples: % moxunit_util_strjoin({'a','b','c'}) -% > 'a b c' +% %|| 'a b c' +% % moxunit_util_strjoin({'a','b','c'}, '>#<') -% > 'a>## 'a b c' % each string of spaces is a tab character +% %|| 'a>## 'a\b\c' % '\\' is the escaped backslash character +% %|| 'a\b\c' +% % moxunit_util_strjoin({'a','b','c'},{'*','='}) -% > 'a*b=c' +% %|| 'a*b=c' % % Notes: % - this function implements similar functionality as matlab's strjoin diff --git a/extern/MOxUnit/Makefile b/extern/MOxUnit/Makefile new file mode 100644 index 00000000000..377f3a777ff --- /dev/null +++ b/extern/MOxUnit/Makefile @@ -0,0 +1,175 @@ +.PHONY: help \ + install-matlab install-octave install \ + uninstall-matlab uninstall-octave uninstall \ + test-matlab test-octave test + +MATLAB?=matlab +OCTAVE?=octave + +TESTDIR=$(CURDIR)/tests +ROOTDIR=$(CURDIR)/MOxUnit +UTILDIR=$(ROOTDIR)/util + +ADDPATH=orig_dir=pwd();cd('$(ROOTDIR)');moxunit_set_path();cd(orig_dir) +RMPATH=rmpath('$(ROOTDIR)'); +SAVEPATH=savepath();exit(0) + +INSTALL=$(ADDPATH);$(SAVEPATH) +UNINSTALL=$(RMPATH);$(SAVEPATH) + +help: + @echo "Usage: make , where is one of:" + @echo "------------------------------------------------------------------" + @echo " install to add MOxUnit to the Matlab and GNU Octave" + @echo " search paths, using whichever is present" + @echo " uninstall to remove MOxUnit from the Matlab and GNU" + @echo " Octave search paths, using whichever is" + @echo " present" + @echo " test to run tests using the Matlab and GNU Octave" + @echo " search paths, whichever is present" + @echo "" + @echo " install-matlab to add MOxUnit to the Matlab search path" + @echo " install-octave to add MOxUnit to the GNU Octave search path" + @echo " uninstall-matlab to remove MOxUnit from the Matlab search path" + @echo " uninstall-octave to remove MOxUnit from the GNU Octave search" + @echo " path" + @echo " test-matlab to run tests using Matlab" + @echo " test-octave to run tests using GNU Octave" + @echo "------------------------------------------------------------------" + @echo "" + @echo "Environmental variables:" + @echo " WITH_COVERAGE Enable line coverage registration" + @echo " JUNIT_XML_FILE Rest results XML output" + @echo " COVER Directory to compute line coverage for" + @echo " COVER_XML_FILE Coverage XML output filename " + @echo " COVER_JSON_FILE Coverage JSON output filename" + @echo " COVER_HTML_DIR Coverage HTML output directory" + @echo " COVER_HTML_DIR Coverage HTML output directory" + @echo " RUN_DOC_TEST If set, run documentation tests" + @echo "" + +RUNTESTS_ARGS?='-verbose' +TEST_RUNNER?=moxunit_runtests + +ifdef RUN_DOC_TEST + RUNTESTS_ARGS+=,'${ROOTDIR}','${UTILDIR}' + TEST_RUNNER=modox_runtests +else + RUNTESTS_ARGS+=,'${TESTDIR}' + ifdef JUNIT_XML_FILE + RUNTESTS_ARGS +=,'-junit_xml_file','$(JUNIT_XML_FILE)' + endif + + ifdef WITH_COVERAGE + ifndef COVER + #$(error COVER variable must be set when using WITH_COVERAGE) + endif + RUNTESTS_ARGS+=,'-with_coverage','-cover','$(COVER)' + export COVER + + ifdef COVER_XML_FILE + RUNTESTS_ARGS+=,'-cover_xml_file','$(COVER_XML_FILE)' + export COVER_XML_FILE + endif + + ifdef COVER_HTML_DIR + RUNTESTS_ARGS+=,'-cover_html_dir','$(COVER_HTML_DIR)' + export COVER_HTML_DIR + endif + + ifdef COVER_JSON_FILE + RUNTESTS_ARGS+=,'-cover_json_file','$(COVER_JSON_FILE)' + export COVER_JSON_FILE + endif + + ifdef JUNIT_XML_FILE + RUNTESTS_ARGS+=,'-junit_xml_file','$(JUNIT_XML_FILE)' + export JUNIT_XML_FILE + endif + endif + TEST_RUNNER=moxunit_runtests +endif + +TEST=$(ADDPATH);success=$(TEST_RUNNER)($(RUNTESTS_ARGS));exit(~success); + +MATLAB_BIN=$(shell which $(MATLAB)) +OCTAVE_BIN=$(shell which $(OCTAVE)) + +ifeq ($(MATLAB_BIN),) + # for Apple OSX, try to locate Matlab elsewhere if not found + MATLAB_BIN=$(shell ls /Applications/MATLAB_R20*/bin/${MATLAB} 2>/dev/null | tail -1) +endif + +MATLAB_RUN=$(MATLAB_BIN) -nojvm -nodisplay -nosplash -r +OCTAVE_RUN=$(OCTAVE_BIN) --no-gui --quiet --eval + +install-matlab: + @if [ -n "$(MATLAB_BIN)" ]; then \ + $(MATLAB_RUN) "$(INSTALL)"; \ + else \ + echo "matlab binary could not be found, skipping"; \ + fi; + +install-octave: + @if [ -n "$(OCTAVE_BIN)" ]; then \ + $(OCTAVE_RUN) "$(INSTALL)"; \ + else \ + echo "octave binary could not be found, skipping"; \ + fi; + +install: + @if [ -z "$(MATLAB_BIN)$(OCTAVE_BIN)" ]; then \ + @echo "Neither matlab binary nor octave binary could be found" \ + exit 1; \ + fi; + $(MAKE) install-matlab + $(MAKE) install-octave + + +uninstall-matlab: + @if [ -n "$(MATLAB_BIN)" ]; then \ + $(MATLAB_RUN) "$(UNINSTALL)"; \ + else \ + echo "matlab binary could not be found, skipping"; \ + fi; + +uninstall-octave: + @if [ -n "$(OCTAVE_BIN)" ]; then \ + $(OCTAVE_RUN) "$(UNINSTALL)"; \ + else \ + echo "octave binary could not be found, skipping"; \ + fi; + +uninstall: + @if [ -z "$(MATLAB_BIN)$(OCTAVE_BIN)" ]; then \ + @echo "Neither matlab binary nor octave binary could be found" \ + exit 1; \ + fi; + $(MAKE) uninstall-matlab + $(MAKE) uninstall-octave + + +test-matlab: + @if [ -n "$(MATLAB_BIN)" ]; then \ + $(MATLAB_RUN) "$(TEST)"; \ + else \ + echo "matlab binary could not be found, skipping"; \ + fi; + +test-octave: + if [ -n "$(OCTAVE_BIN)" ]; then \ + $(OCTAVE_RUN) "$(TEST)"; \ + else \ + echo "octave binary could not be found, skipping"; \ + fi; + +test: + @if [ -z "$(MATLAB_BIN)$(OCTAVE_BIN)" ]; then \ + @echo "Neither matlab binary nor octave binary could be found" \ + exit 1; \ + fi; + $(MAKE) test-matlab + $(MAKE) test-octave + + + diff --git a/extern/MOxUnit/README.md b/extern/MOxUnit/README.md index e1a67a1d9b6..7d9991e38dd 100644 --- a/extern/MOxUnit/README.md +++ b/extern/MOxUnit/README.md @@ -1,19 +1,28 @@ -# MOxUnit [![Build Status](https://travis-ci.org/nno/MOxUnit.svg?branch=master)](https://travis-ci.org/MOxUnit/MOxUnit) [![Coverage Status](https://coveralls.io/repos/github/MOxUnit/MOxUnit/badge.svg?branch=master)](https://coveralls.io/github/MOxUnit/MOxUnit?branch=master) +# MOxUnit [![Build Status](https://travis-ci.org/nno/MOxUnit.svg?branch=master)](https://travis-ci.org/MOxUnit/MOxUnit) ![Test](https://github.com/MOxUnit/MOxUnit/workflows/CI/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/MOxUnit/MOxUnit/badge.svg?branch=master)](https://coveralls.io/github/MOxUnit/MOxUnit?branch=master) MOxUnit is a lightweight unit test framework for Matlab and GNU Octave. -### Features +- [Features](#features) +- [Installation](#installation) +- [Defining MOxUnit tests](#defining-moxunit-tests) +- [Running MOxUnit tests](#running-moxunit-tests) +- [Use with CI](#use-with-ci) + - [Octave](#octave) + - [Matlab](#matlab) +- [Compatibility notes](#compatibility-notes) +- [Limitations](#limitations) + +## Features - Runs on both the [Matlab] and [GNU Octave] platforms. -- Uses object oriented TestCase, TestSuite and TestResult classes, allowing for user-defined extensions. +- Uses object-oriented TestCase, TestSuite and TestResult classes, allowing for user-defined extensions. - Can be used directly with continuous integration services, such as [Travis-ci] and [Shippable]. - Supports JUnit-like XML output for use with Shippable and other test results visualization approaches. - Supports the generation of code coverage reports using [MOCov] -- Provides compatibility with Steve Eddin's [Matlab xUnit test framework]. +- Provides compatibility with the (now unsupported) Steve Eddin's [Matlab xUnit test framework], and with recent Matlab test functionality. - Distributed under the MIT license, a permissive free software license. - -### Installation +## Installation - Using the shell (requires a Unix-like operating system such as GNU/Linux or Apple OSX): @@ -26,75 +35,64 @@ MOxUnit is a lightweight unit test framework for Matlab and GNU Octave. - Manual installation: - + Download the zip archive from the [MOxUnit] website. + + Download the [[MOxUnit zip archive] from the [MOxUnit] website, and extract it. This should + result in a directory called ``MOxUnit-master``. + Start Matlab or GNU Octave. - + On the Matlab or GNU Octave prompt, `cd` to the `MOxUnit` root directory, then run: + + On the Matlab or GNU Octave prompt, go to the directory that contains the new ``MOxUnit-master`` directory, then run: ```matlab - cd MOxUnit % cd to MOxUnit subdirectory - moxunit_set_path() % add the current directory to the Matlab/GNU Octave path - savepath % save the path - ``` - -### Running MOxUnit tests - -- `cd` to the directory where the unit tests reside. For MOxUnit itself, the unit tests are in the directory `tests`. -- run the tests using `moxunit_runtests`. For example, running `moxunit_runtests` from MOxUnit's `tests` directory should give the following output: - - ``` - suite: 31 tests - ............................... - -------------------------------------------------- - - OK - ans = + % change to the MOxUnit subdirectory + % + % Note: if MOxUnit was retrieved using 'git', then the name of + % top-level directory is 'MOxUnit', not 'MOxUnit-master' + cd MOxUnit-master/MOxUnit - 1 - ``` + % add the current directory to the Matlab/GNU Octave path + moxunit_set_path() -- `moxunit_runtests`, by default, gives non-verbose output and runs all tests in the current directory. This can be changed using the following arguments: - - `-verbose`: show verbose output. - - `directory`: run unit tests in directory `directory`. - - `file.m`: run unit tests in file `file.m`. - - `-recursive`: add files from directories recursively. - - `-logfile logfile.txt`: store the output in file `logfile.txt`. - - `-junit_xml_file xmlfile`: store JUnit-like XML output in file `xmlfile`. - -- To test MOxUnit itself using a shell, run: - - ``` - make test - ``` - -### Use with travis-ci and Shippable -MOxUnit uses the [Travis-ci] service for continuous integration testing. This is achieved by setting up a [.travis.yml configuration file](.travis.yml). This file is also used by [Shippable]. -As a result, the test suite is run automatically on both [Travis-ci] and [Shippable] every time it is pushed to the github repository, or when a pull request is made. If a test fails, or if all tests pass after a test failed before, the developers are notified by email. + % save the path + savepath + ``` -### Defining MOxUnit tests +## Defining MOxUnit tests To define unit tests, write a function with the following header: ```matlab -function test_suite=my_test_of_abs +function test_suite=test_of_abs + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end initTestSuite; ``` -*Important*: it is crucial that the output of the main function is called `test_suite`. +### Important + +- It is crucial that the output of the main function is a variable named `test_suite`, and that the output of `localfunctions` is assigned to a variable named `test_functions`. +- As of Matlab 2016b, Matlab scripts (such as `initTestSuite.m`) do not have access to subfunctions in a function if called from that function. Therefore it requires using localfunctions to obtain function handles to local functions. The "try-catch-end" statements are necessary for compatibility with older versions of GNU Octave, which do not provide the `localfunctions` function. +- Alas, the call to `localfunctions` **cannot** be incorporated into `initTestSuite` so the entire code snippet above has to be the header of each test file + +Then, define subfunctions whose name start with `test` or end with `test` (case-insensitive). These functions can use the following `assert*` functions: -Then, define subfunctions whose name start with `test_` or end with `_test`. These functions can use the following `assert*` functions: - `assertTrue(a)`: assert that `a` is true. - `assertFalse(a)`: assert that `a` is false. - `assertEqual(a,b)`: assert that `a` and `b` are equal. - `assertElementsAlmostEqual(a,b)`: assert that the floating point arrays `a` and `b` have the same size, and that corresponding elements are equal within some numeric tolerance. - `assertVectorsAlmostEqual(a,b)`: assert that floating point vectors `a` and `b` have the same size, and are equal within some numeric tolerance based on their vector norm. -- `assertExceptionThrown(f,id)`: assert that calling `f()` throws an exception with identifier `id`. (To deal with cases where Matlab and GNU Octave throw errors with different identifiers, use `moxunit_util_platform_is_octave`). +- `assertExceptionThrown(f,id)`: assert that calling `f()` throws an exception with identifier `id`. (To deal with cases where Matlab and GNU Octave throw errors with different identifiers, use `moxunit_util_platform_is_octave`. Or use `id='*'` to match any identifier). As a special case, `moxunit_throw_test_skipped_exception('reason')` throws an exception that is caught when running the test; `moxunit_run_tests` will report that the test is skipped for reason `reason`. For example, the following function defines three unit tests that tests some possible inputs from the builtin `abs` function: + ```matlab -function test_suite=my_test_of_abs - initTestSuite +function test_suite=test_of_abs + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; function test_abs_scalar assertTrue(abs(-1)==1) @@ -118,34 +116,155 @@ function test_abs_exceptions Examples of unit tests are in MOxUnit's `tests` directory, which test some of MOxUnit's functions itself. -### Compatibility notes +## Running MOxUnit tests + +- `cd` to the directory where the unit tests reside. For MOxUnit itself, the unit tests are in the directory `tests`. +- run the tests using `moxunit_runtests`. For example, running `moxunit_runtests` from MOxUnit's `tests` directory runs tests for MOxUnit itself, and should give the following output: + + ```matlab + >> moxunit_runtests + suite: 98 tests + ............................................................ + ...................................... + -------------------------------------------------- + + OK (passed=98) + ans = + logical + 1 + ``` + +- `moxunit_runtests`, by default, gives non-verbose output and runs all tests in the current directory. This can be changed using the following arguments: + - `-verbose`: show verbose output. + - `-quiet`: supress all output + - `directory`: run unit tests in directory `directory`. + - `file.m`: run unit tests in file `file.m`. + - `-recursive`: add files from directories recursively. + - `-logfile logfile.txt`: store the output in file `logfile.txt`. + - `-junit_xml_file xmlfile`: store JUnit-like XML output in file `xmlfile`. + +- To test MOxUnit itself from a terminal, run: + + ``` + make test + ``` + +## Use with CI + +MOxUnit can be used with the [Travis-ci] service for continuous integration (CI) testing. This is achieved by setting up a [.travis.yml configuration file](.travis.yml). This file is also used by [Shippable]. As a result, the test suite is run automatically on both [Travis-ci] and [Shippable] every time it is pushed to the github repository, or when a pull request is made. If a test fails, or if all tests pass after a test failed before, the developers are notified by email. + +### Octave + +The easiest test to set up on Travis and/or Shippable is with [GNU Octave]. Make sure your code is Octave compatible. Note that many Matlab projects tend to use functionality not present in Octave (such as particular functions), whereasand writing code that is both Matlab- and Octave-compatible may require some additional efforts. + +A simple `.travis.yml` file for a project could look like that: + +```yaml +language: generic +os: linux + +before_install: + - sudo apt-get install octave + +before_script: + - git clone https://github.com/MOxUnit/MOxUnit.git + - make -C MOxUnit install + +script: + - make test +``` + +In this case `make test` is used to run the tests. To avoid a Makefile and run tests directly through Octave, the script has to call Octave directly to run the tests: + + ```yaml + # ... + before_script: + - git clone https://github.com/MOxUnit/MOxUnit.git + + script: + - octave --no-gui --eval "addpath('~/git/MOxUnit/MOxUnit');moxunit_set_path;moxunit_runtests('tests')" + ``` + +Note that MOxUnit tests **itself** on travis, with [this](https://github.com/MOxUnit/MOxUnit/blob/master/.travis.yml) travis file. + +### Matlab + +Travis [now supports Matlab](https://docs.travis-ci.com/user/languages/matlab/) directly. You can use MOxUnit with it, but its tricky because: + 1) Travis only supports Matlab 2020a and, presumably, higher (at the time of writing 2020a is the newest version). + 2) Makefile installation does not work with Matlab on travis. + 3) Nor does calling Matlab from the command line in a usual way - with ` matlab -nodesktop -nosplash ...` . Instead it has to be called with the `-batch` flag. + + Therefore, `.travis.yml` file looks as follows: + ```yml + language: matlab + matlab: R2020a + os: linux + + # Just clone MOxUnit, `don't make install` it (!) + before_script: + - git clone https://github.com/MOxUnit/MOxUnit.git + + script: + - matlab -batch 'back=cd("./MOxUnit/MOxUnit/"); moxunit_set_path(); cd(back); moxunit_runtests tests -verbose; exit(double(~ans))' + ``` + + `exit(double(~ans))` ensures that the build fails if MOxUnit tests fail. + +## Compatibility notes + - Because GNU Octave 3.8 does not support `classdef` syntax, 'old-style' object-oriented syntax is used for the class definitions. For similar reasons, MOxUnit uses the `lasterror` function, even though its use in Matlab is discouraged. +- Recent versions of Matlab (2016 and later) do not support tests defined just using "initTestSuite", that is without the use of `localfunctions` (see above). To ease the transition, consider using the Python script `tools/fix_mfile_test_init.py`, which can update existing .m files that do not use `localfunctions`. + For example, the following command was used on a Unix-like shell to preview changes to MOxUnit's tests: -### Acknowledgements -- The object-oriented class structure was inspired by the [Python unit test] framework. -- The `assert*` function signatures are aimed to be compatible with Steve Eddin's [Matlab xUnit test framework]. + ```bash + find tests -iname 'test*.m' | xargs -L1 tools/fix_mfile_test_init.py + ``` + and adding the `--apply` option applies these changes, meaning that found files are rewritten: + + ```bash + find tests -iname 'test*.m' | xargs -L1 tools/fix_mfile_test_init.py --apply + ``` +- Recent versions of Matlab define a `matlab.unittest.Test` class for unit tests. An instance `t` can be used with MOxUnit using the `MOxUnitMatlabUnitWrapperTestCase(t)`, which is a `MOxUnitTestCase` instance. Tests that are defined through + + ```matlab + function tests=foo() + tests=functiontests(localfunctions) + + function test_funcA(param) + + function test_funcA(param) + ``` + + can be run using MOxUnit as well (and included in an ``MOxUnitTestSuite`` instance using its with ``addFile``) instance, with the exception that currently setup and teardown functions are currently ignored. + +## Limitations -### Limitations Currently MOxUnit does not support: -- Documentation tests (these would require `evalc`, which is not available on `GNU Octave` as of January 2014). +- Documentation tests require [MOdox]. - Support for setup and teardown functions in `TestCase` classes. +- Subclasses of MOxUnit's classes (`MOxUnitTestCase`, `MOxUnitTestSuite`, `MOxUnitTestReport`) have to be defined using "old-style" object-oriented syntax. +- Subtests +## Acknowledgements + +- The object-oriented class structure was inspired by the [Python unit test] framework. +- The `assert*` function signatures are aimed to be compatible with Steve Eddin's [Matlab xUnit test framework]. -### Contact -Nikolaas N. Oosterhof, nikolaas dot oosterhof at unitn dot it +## Contact +Nikolaas N. Oosterhof, n dot n dot oosterhof at googlemail dot com. -### Contributions -- Thanks to Scott Lowe, Thomas Feher and Joel LeBlanc for contributions. +## Contributions +- Thanks to Scott Lowe, Thomas Feher, Joel LeBlanc, Anderson Bravalheri, Sven Baars, 'jdbancal', Marcin Konowalczyk for contributions. -### License +## License (The MIT License) -Copyright (c) 2015 Nikolaas N. Oosterhof +Copyright (c) 2015-2020 Nikolaas N. Oosterhof Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -167,15 +286,13 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - [GNU Octave]: http://www.gnu.org/software/octave/ [Matlab]: http://www.mathworks.com/products/matlab/ [Matlab xUnit test framework]: http://it.mathworks.com/matlabcentral/fileexchange/22846-matlab-xunit-test-framework +[MOdox]: https://github.com/MOdox/MOdox [MOxUnit]: https://github.com/MOxUnit/MOxUnit +[MOxUnit zip archive]: https://github.com/MOxUnit/MOxUnit/archive/master.zip [MOcov]: https://github.com/MOcov/MOcov [Python unit test]: https://docs.python.org/2.6/library/unittest.html [Travis-ci]: https://travis-ci.org [Shippable]: https://app.shippable.com/ - - - diff --git a/extern/MOxUnit/tests/test_assert_elements_almost_equal.m b/extern/MOxUnit/tests/test_assert_elements_almost_equal.m new file mode 100644 index 00000000000..c9e186b6dc3 --- /dev/null +++ b/extern/MOxUnit/tests/test_assert_elements_almost_equal.m @@ -0,0 +1,57 @@ +function test_suite=test_assert_elements_almost_equal() + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_assert_elements_almost_equal_exceptions + assertExceptionThrown(@()assertElementsAlmostEqual(... + [],'a'),... + 'assertElementsAlmostEqual:sizeMismatch'); + assertExceptionThrown(@()assertElementsAlmostEqual(... + [1 2],[1;2]),... + 'assertElementsAlmostEqual:sizeMismatch'); + assertExceptionThrown(@()assertElementsAlmostEqual(... + [1 2],[1 3]),... + 'assertElementsAlmostEqual:tolExceeded'); + assertExceptionThrown(@()assertElementsAlmostEqual(... + 0,1e-10,'absolute',1e-11),... + 'assertElementsAlmostEqual:tolExceeded'); + assertExceptionThrown(@()assertElementsAlmostEqual(... + 0,1e-24,'relative',0,0),... + 'assertElementsAlmostEqual:tolExceeded'); + assertExceptionThrown(@()assertElementsAlmostEqual(... + NaN,Inf),... + 'assertElementsAlmostEqual:tolExceeded'); + assertExceptionThrown(@()assertElementsAlmostEqual(... + -Inf,Inf),... + 'assertElementsAlmostEqual:tolExceeded'); + assertExceptionThrown(@()assertElementsAlmostEqual(... + struct(),struct()),... + 'assertElementsAlmostEqual:notFloat'); + assertExceptionThrown(@()assertElementsAlmostEqual(... + {1,'a'},{1,'a'}),... + 'assertElementsAlmostEqual:notFloat'); + assertExceptionThrown(@()assertElementsAlmostEqual(... + 1,1,'foo',1),... + 'compareFloats:unrecognizedToleranceType'); + + + + +function test_assert_elements_almost_equal_passes + assertElementsAlmostEqual(0,0); + assertElementsAlmostEqual(0,1e-10); + assertElementsAlmostEqual(0,1,'relative',1); + assertElementsAlmostEqual(0,1,'absolute',1); + assertElementsAlmostEqual(1:6,1e-10+(1:6)); + assertElementsAlmostEqual([1 2;3 4],1e-10+[1 2; 3 4]); + assertElementsAlmostEqual(NaN,NaN); + assertElementsAlmostEqual(NaN,NaN,'relative',0); + assertElementsAlmostEqual(NaN,NaN,'absolute',0); + assertElementsAlmostEqual(Inf,Inf); + assertElementsAlmostEqual([-Inf,Inf,NaN,2],[-Inf,Inf,NaN,2]); + assertElementsAlmostEqual(0,sparse(0)); + assertElementsAlmostEqual(double(0),single(0)); + diff --git a/extern/MOxUnit/tests/test_assert_equal.m b/extern/MOxUnit/tests/test_assert_equal.m new file mode 100644 index 00000000000..73a24f39236 --- /dev/null +++ b/extern/MOxUnit/tests/test_assert_equal.m @@ -0,0 +1,25 @@ +function test_suite=test_assert_equal() + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_assert_equal_exceptions + assertExceptionThrown(@()assertEqual(... + [1],'a'),... + 'assertEqual:classNotEqual'); + assertExceptionThrown(@()assertEqual(... + [1 2],[1;2]),... + 'assertEqual:nonEqual'); + assertExceptionThrown(@()assertEqual(... + sparse(1),1),... + 'assertEqual:sparsityNotEqual'); + assertExceptionThrown(@()assertEqual(... + [1 2],[1 3]),... + 'assertEqual:nonEqual'); + +function test_assert_equal_passes + assertEqual(1,1); + assertEqual(struct(),struct()); + assertEqual({1,'a'},{1,'a'}); diff --git a/extern/MOxUnit/tests/test_assert_exception_thrown.m b/extern/MOxUnit/tests/test_assert_exception_thrown.m new file mode 100644 index 00000000000..bc1aa3a2485 --- /dev/null +++ b/extern/MOxUnit/tests/test_assert_exception_thrown.m @@ -0,0 +1,128 @@ +function test_suite=test_assert_exception_thrown() + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +% Test cases where exceptions are thrown and that is OK +function test_assert_exception_thrown_passes + assertExceptionThrown(@()error('Throw w/o ID')); + assertExceptionThrown(@()error('moxunit:error','msg'),... + 'moxunit:error'); + assertExceptionThrown(@()error('moxunit:error','msg'),... + 'moxunit:error','message'); + assertExceptionThrown(@()error('moxunit:error','msg'),... + 'moxunit:error'); + assertExceptionThrown(@()error('moxunit:error','msg'),... + '*','message'); % Same as above + assertExceptionThrown(@()error('Throw w/o ID'),... + '*','message'); % Same as above + assertExceptionThrown(@()error('moxunit:error','msg'),... + '*'); % Any error OK + assertExceptionThrown(@()error('Throw w/o ID'),... + '*'); % Same as above + + % Explicitly assert that an error is thrown without an ID + assertExceptionThrown(@()error('Throw w/o ID'),... + ''); + + % Allow id to be a cellstr + assertExceptionThrown(@()error('moxunit:error','msg'),... + {'moxunit:error'},'message'); + assertExceptionThrown(@()error('Throw w/o ID'),... + '*','message'); + assertExceptionThrown(@()error('moxunit:error','msg'),... + {'moxunit:foo','moxunit:error','moxunit:foo'}); + assertExceptionThrown(@()error('Throw w/o ID'),... + {'','moxunit:baz'}); + +function test_assert_exception_thrown_illegal_arguments + args_cell={ ... + {@()error('foo'),'message'},... + {@()error('foo'),'not:id:_entifier','message'},... + {@()error('foo'),struct},... + {@()error('foo'),9},... + {@()error('foo'),'error:id',9},... + {@()error('foo'),'error:id',struct},... + {@()error('foo'),'error_id','message'},... + {'foo'},... + }; + + for k=1:numel(args_cell) + args=args_cell{k}; + try + assertExceptionThrown(args{:}) + + error_exception_not_thrown('moxunit:illegalParameter'); + catch + [unused,error_id]=lasterr(); + error_if_wrong_id_thrown('moxunit:illegalParameter',error_id); + end + end + + +% Test cases where func throws exceptions and we need to throw as well +function test_assert_exception_thrown_wrong_exception + % Verify that when func throws but the wrong exception comes out, we respond + % with the correct exception (assertExceptionThrown:wrongException) + exception_id_cell = {'moxunit:failed',... + '',... + {'','moxunit:failed'},... + }; + + for k=1:numel(exception_id_cell) + exception_id=exception_id_cell{k}; + + try + assertExceptionThrown(@()error('moxunit:error','msg'),... + exception_id,'msg'); + error_exception_not_thrown('moxunit:wrongExceptionRaised'); + catch + [unused,error_id]=lasterr(); + error_if_wrong_id_thrown('moxunit:wrongExceptionRaised',... + error_id); + end + end + +% Test cases where func does not throw but was expected to do so +function test_assert_exception_thrown_exceptions_not_thrown + + % For all combination of optional arguments we expect the same + % behavior. We will loop, testing all combinations here + args_cell = {... + {@do_nothing},... % No arguments + {@do_nothing,'moxunit:failed'},... % Identifier only + {@do_nothing,'*','message'},... % Wildcard + {@do_nothing,'a:b','msg'},... % All arguments + {@do_nothing,{'a:b','c:d'},'msg'} % Cell str + }; + + for k=1:numel(args_cell) + args=args_cell{k}; + + % Run the test + try + assertExceptionThrown(args{:}); + error_exception_not_thrown('moxunit:exceptionNotRaised'); + catch + [unused,error_id]=lasterr(); + error_if_wrong_id_thrown('moxunit:exceptionNotRaised',... + error_id); + end + end + + +function error_exception_not_thrown(error_id) + error('moxunit:exceptionNotRaised', 'Exception ''%s'' not thrown', error_id); + +function error_if_wrong_id_thrown(expected_error_id, thrown_error_id) + if ~strcmp(thrown_error_id, expected_error_id) + error('moxunit:wrongExceptionRaised',... + 'Exception raised with id ''%s'' expected id ''%s''',... + thrown_error_id,expected_error_id); + end + + +function do_nothing + % do nothing \ No newline at end of file diff --git a/extern/MOxUnit/tests/test_assert_false.m b/extern/MOxUnit/tests/test_assert_false.m new file mode 100644 index 00000000000..08d2351c1ac --- /dev/null +++ b/extern/MOxUnit/tests/test_assert_false.m @@ -0,0 +1,20 @@ +function test_suite=test_assert_false() + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_assert_false_exceptions + assertExceptionThrown(@()assertFalse(... + true),... + 'assertFalse:trueCondition'); + assertExceptionThrown(@()assertFalse(... + struct),... + 'assertFalse:invalidCondition'); + assertExceptionThrown(@()assertFalse(... + [false false]),... + 'assertFalse:invalidCondition'); + +function test_assert_false_passes + assertFalse(false); diff --git a/extern/MOxUnit/tests/test_assert_greater_than.m b/extern/MOxUnit/tests/test_assert_greater_than.m new file mode 100644 index 00000000000..30f49f4ab77 --- /dev/null +++ b/extern/MOxUnit/tests/test_assert_greater_than.m @@ -0,0 +1,38 @@ +function test_suite=test_assert_greater_than() + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_assert_greater_than_exceptions + assertExceptionThrown(@()assertGreaterThan([1 2],[1,2,3]),... + 'assertGreaterThan:sizeNotEqual'); + assertExceptionThrown(@()assertGreaterThan([1,1],[0;0]),... + 'assertGreaterThan:sizeNotEqual'); + assertExceptionThrown(@()assertGreaterThan([1],'a'), ... + 'assertGreaterThan:classNotEqual'); + assertExceptionThrown(@()assertGreaterThan(1,1), ... + 'assertGreaterThan:notGreaterThan'); + assertExceptionThrown(@()assertGreaterThan([2,3,3],[1,2,3]), ... + 'assertGreaterThan:notGreaterThan'); + assertExceptionThrown(@()assertGreaterThan(nan,nan), ... + 'assertGreaterThan:notGreaterThan'); + assertExceptionThrown(@()assertGreaterThan(nan,1), ... + 'assertGreaterThan:notGreaterThan'); + assertExceptionThrown(@()assertGreaterThan(1,nan), ... + 'assertGreaterThan:notGreaterThan'); + assertExceptionThrown(@()assertGreaterThan(ones(2,2,2,2),ones(2,2,2,2)*nan), ... + 'assertGreaterThan:notGreaterThan'); + + a = zeros(2,2,2,2); + assertExceptionThrown(@()assertGreaterThan(a,ones(2,2,2,2)), ... + 'assertGreaterThan:notGreaterThan'); + a(1,1,1,1) = 1; + assertExceptionThrown(@()assertGreaterThan(a,ones(2,2,2,2)), ... + 'assertGreaterThan:notGreaterThan'); + +function test_assert_greater_than_passes + assertGreaterThan(1,0); + assertGreaterThan([1,1],[0,0]); + assertGreaterThan(ones(2,2,2,2),zeros(2,2,2,2)); diff --git a/extern/MOxUnit/tests/test_assert_less_than.m b/extern/MOxUnit/tests/test_assert_less_than.m new file mode 100644 index 00000000000..fd914ce4e0e --- /dev/null +++ b/extern/MOxUnit/tests/test_assert_less_than.m @@ -0,0 +1,38 @@ +function test_suite=test_assert_less_than() + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_assert_less_than_exceptions + assertExceptionThrown(@()assertLessThan([1 2],[1,2,3]),... + 'assertLessThan:sizeNotEqual'); + assertExceptionThrown(@()assertLessThan([0,0],[1;1]),... + 'assertLessThan:sizeNotEqual'); + assertExceptionThrown(@()assertLessThan([1],'a'), ... + 'assertLessThan:classNotEqual'); + assertExceptionThrown(@()assertLessThan(1,1), ... + 'assertLessThan:notLessThan'); + assertExceptionThrown(@()assertLessThan([1,2,3],[2,3,3]), ... + 'assertLessThan:notLessThan'); + assertExceptionThrown(@()assertLessThan(nan,nan), ... + 'assertLessThan:notLessThan'); + assertExceptionThrown(@()assertLessThan(nan,1), ... + 'assertLessThan:notLessThan'); + assertExceptionThrown(@()assertLessThan(1,nan), ... + 'assertLessThan:notLessThan'); + assertExceptionThrown(@()assertLessThan(ones(2,2,2,2),ones(2,2,2,2)*nan), ... + 'assertLessThan:notLessThan'); + + a = zeros(2,2,2,2); + assertExceptionThrown(@()assertLessThan(ones(2,2,2,2),a), ... + 'assertLessThan:notLessThan'); + a(1,1,1,1) = 1; + assertExceptionThrown(@()assertLessThan(ones(2,2,2,2),a), ... + 'assertLessThan:notLessThan'); + +function test_assert_less_than_passes + assertLessThan(0,1); + assertLessThan([0,0],[1,1]); + assertLessThan(zeros(2,2,2,2),ones(2,2,2,2)); diff --git a/extern/MOxUnit/tests/test_assert_not_equal.m b/extern/MOxUnit/tests/test_assert_not_equal.m new file mode 100644 index 00000000000..c1cceefe98b --- /dev/null +++ b/extern/MOxUnit/tests/test_assert_not_equal.m @@ -0,0 +1,21 @@ +function test_suite=test_assert_not_equal() + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_assert_not_equal_exceptions + assertExceptionThrown(@()assertNotEqual(... + [1],[1]),... + 'assertNotEqual:equal'); + assertExceptionThrown(@()assertNotEqual(... + struct(),struct()),... + 'assertNotEqual:equal'); + +function test_assert_not_equal_passes + assertNotEqual(1,2); + assertNotEqual([1 2],[1;2]); + assertNotEqual(sparse(1),1); + assertNotEqual([1 2],[1 3]); + assertNotEqual('a','b'); diff --git a/extern/MOxUnit/tests/test_assert_true.m b/extern/MOxUnit/tests/test_assert_true.m new file mode 100644 index 00000000000..9263868e62e --- /dev/null +++ b/extern/MOxUnit/tests/test_assert_true.m @@ -0,0 +1,22 @@ +function test_suite=test_assert_true() + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_assert_true_exceptions + + assertExceptionThrown(@()assertTrue(... + false),... + 'assertTrue:falseCondition'); + assertExceptionThrown(@()assertTrue(... + struct),... + 'assertTrue:invalidCondition'); + assertExceptionThrown(@()assertTrue(... + [true true]),... + 'assertTrue:invalidCondition'); + +function test_assert_true_passes + assertTrue(true); + diff --git a/extern/MOxUnit/tests/test_assert_vectors_almost_equal.m b/extern/MOxUnit/tests/test_assert_vectors_almost_equal.m new file mode 100644 index 00000000000..93c82053065 --- /dev/null +++ b/extern/MOxUnit/tests/test_assert_vectors_almost_equal.m @@ -0,0 +1,75 @@ +function test_suite=test_assert_vectors_almost_equal() + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_assert_vectors_almost_equal_size_exceptions + + + assertExceptionThrown(@()assertVectorsAlmostEqual(... + [1 2],[1;2]),... + 'assertVectorsAlmostEqual:sizeMismatch'); + + +function test_assert_vectors_almost_equal_tolerance_exceptions + assertExceptionThrown(@()assertVectorsAlmostEqual(... + [1 2],[1 3]),... + 'assertVectorsAlmostEqual:tolExceeded'); + assertExceptionThrown(@()assertVectorsAlmostEqual(... + 0,1e-10,'absolute',1e-11),... + 'assertVectorsAlmostEqual:tolExceeded'); + assertExceptionThrown(@()assertVectorsAlmostEqual(... + 0,1e-24,'relative',0,0),... + 'assertVectorsAlmostEqual:tolExceeded'); + assertExceptionThrown(@()assertVectorsAlmostEqual(... + NaN,NaN),... + 'assertVectorsAlmostEqual:tolExceeded'); + assertExceptionThrown(@()assertVectorsAlmostEqual(... + -Inf,-Inf),... + 'assertVectorsAlmostEqual:tolExceeded'); + + +function test_assert_vectors_almost_equal_not_float_exception + assertExceptionThrown(@()assertVectorsAlmostEqual(... + [1],'a'),... + 'assertVectorsAlmostEqual:notFloat'); + assertExceptionThrown(@()assertVectorsAlmostEqual(... + 'a',1),... + 'assertVectorsAlmostEqual:notFloat'); + + assertExceptionThrown(@()assertVectorsAlmostEqual(... + struct(),struct()),... + 'assertVectorsAlmostEqual:notFloat'); + assertExceptionThrown(@()assertVectorsAlmostEqual(... + {1,'a'},{1,'a'}),... + 'assertVectorsAlmostEqual:notFloat'); + + +function test_assert_vectors_almost_equal_tol_type_exception + assertExceptionThrown(@()assertVectorsAlmostEqual(... + 1,1,'foo',1),... + 'compareFloats:unrecognizedToleranceType'); + + +function test_assert_vectors_almost_equal_not_vector_exception + % note: xUnit does not throw an exception here + assertExceptionThrown(@()assertVectorsAlmostEqual(... + [1 2;3 4],1e-10+[1 2; 3 4]),... + 'assertVectorsAlmostEqual:notVector'); + + + +function test_assert_vectors_almost_equal_passes + assertVectorsAlmostEqual(0,0); + + a=randn(); + assertVectorsAlmostEqual(a,a); + + assertVectorsAlmostEqual(0,1e-10); + assertVectorsAlmostEqual(0,1,'relative',1); + assertVectorsAlmostEqual(0,1,'absolute',1); + assertVectorsAlmostEqual(1:6,1e-10+(1:6)) + assertVectorsAlmostEqual(1:6,sparse(1e-10+(1:6))) + diff --git a/extern/MOxUnit/tests/test_assert_warning.m b/extern/MOxUnit/tests/test_assert_warning.m new file mode 100644 index 00000000000..9016a64cd0f --- /dev/null +++ b/extern/MOxUnit/tests/test_assert_warning.m @@ -0,0 +1,124 @@ +function test_suite=test_assert_warning() + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +% Test cases where warnings are thrown and that is OK +function test_assert_warning_passes + assertWarning(@()warning('Throw w/o ID')); + assertWarning(@()warning('moxunit:warning','msg'),... + 'moxunit:warning'); + assertWarning(@()warning('moxunit:warning','msg'),... + 'moxunit:warning','message'); + assertWarning(@()warning('moxunit:warning','msg'),... + 'moxunit:warning'); + assertWarning(@()warning('moxunit:warning','msg'),... + '*','message'); % Same as above + assertWarning(@()warning('Throw w/o ID'),... + '*','message'); % Same as above + assertWarning(@()warning('moxunit:warning','msg'),... + '*'); % Any warning OK + assertWarning(@()warning('Throw w/o ID'),... + '*'); % Same as above + + % Allow id to be a cellstr + assertWarning(@()warning('moxunit:warning','msg'),... + {'moxunit:warning'},'message'); + assertWarning(@()warning('Throw w/o ID'),... + '*','message'); + assertWarning(@()warning('moxunit:warning','msg'),... + {'moxunit:foo','moxunit:warning','moxunit:foo'}); + assertWarning(@()warning('Throw w/o ID'),... + {'','moxunit:baz'}); + +function test_assert_warning_illegal_arguments + args_cell={ ... + {@()warning('foo'),'message'},... + {@()warning('foo'),'not:id:_entifier','message'},... + {@()warning('foo'),struct},... + {@()warning('foo'),9},... + {@()warning('foo'),'warning:id',9},... + {@()warning('foo'),'warning:id',struct},... + {@()warning('foo'),'warning_id','message'},... + {'foo'},... + }; + + for k=1:numel(args_cell) + args=args_cell{k}; + try + assertWarning(args{:}) + + error_warning_not_thrown('moxunit:illegalParameter'); + catch + [unused,warning_id]=lasterr(); + error_if_wrong_id_thrown('moxunit:illegalParameter',warning_id); + end + end + + +% Test cases where func throws warnings and we need to throw as well +function test_assert_warning_wrong_warning + % Verify that when func throws but the wrong warning comes out, we respond + % with the correct warning (assertWarning:wrongWarning) + warning_id_cell = {'moxunit:failed',... + '',... + {'','moxunit:failed'},... + }; + + for k=1:numel(warning_id_cell) + warning_id=warning_id_cell{k}; + + try + assertWarning(@()warning('moxunit:warning','msg'),... + warning_id,'msg'); + error_warning_not_thrown('moxunit:wrongWarningRaised'); + catch + [unused,warning_id]=lasterr(); + error_if_wrong_id_thrown('moxunit:wrongWarningRaised',... + warning_id); + end + end + +% Test cases where func does not throw but was expected to do so +function test_assert_warning_warnings_not_thrown + + % For all combination of optional arguments we expect the same + % behavior. We will loop, testing all combinations here + args_cell = {... + {@do_nothing},... % No arguments + {@do_nothing,'moxunit:failed'},... % Identifier only + {@do_nothing,'*','message'},... % Wildcard + {@do_nothing,'a:b','msg'},... % All arguments + {@do_nothing,{'a:b','c:d'},'msg'} % Cell str + }; + + for k=1:numel(args_cell) + args=args_cell{k}; + + % Run the test + try + assertWarning(args{:}); + error_warning_not_thrown('moxunit:warningNotRaised'); + catch + [unused,warning_id]=lasterr(); + error_if_wrong_id_thrown('moxunit:warningNotRaised',... + warning_id); + end + end + + +function error_warning_not_thrown(warning_id) + error('moxunit:warningNotRaised', 'Warning ''%s'' not thrown', warning_id); + +function error_if_wrong_id_thrown(expected_warning_id, thrown_warning_id) + if ~strcmp(thrown_warning_id, expected_warning_id) + error('moxunit:wrongWarningRaised',... + 'Warning raised with id ''%s'' expected id ''%s''',... + thrown_warning_id, expected_warning_id); + end + + +function do_nothing + % do nothing \ No newline at end of file diff --git a/extern/MOxUnit/tests/test_function_handle_test_case.m b/extern/MOxUnit/tests/test_function_handle_test_case.m new file mode 100644 index 00000000000..db295db42f4 --- /dev/null +++ b/extern/MOxUnit/tests/test_function_handle_test_case.m @@ -0,0 +1,85 @@ +function test_suite=test_function_handle_test_case +% tests for MOxUnitFunctionHandleTestCase + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function s=randstr() + s=char(20*rand(1,10)+65); + +function test_function_handle_test_case_basics + + outcome_class2func=struct(); + outcome_class2func.MOxUnitPassedTestOutcome=@do_nothing; + outcome_class2func.MOxUnitSkippedTestOutcome=@()... + moxunit_throw_test_skipped_exception('foo'); + outcome_class2func.MOxUnitFailedTestOutcome=@()error('here'); + + keys=fieldnames(outcome_class2func); + for k=1:numel(keys) + outcome_class=keys{k}; + func=outcome_class2func.(outcome_class); + + name=randstr(); + location=randstr(); + + f=MOxUnitFunctionHandleTestCase(name, location, func); + assertEqual(getName(f),name); + assertEqual(getLocation(f),location); + assertEqual(str(f),sprintf('%s: %s',name,location)); + + rep=MOxUnitTestReport(0,1); + rep=run(f,rep); + + assertEqual(countTestOutcomes(rep),1); + outcome=getTestOutcome(rep,1); + + assertEqual(class(outcome),outcome_class); + end + +function test_function_handle_test_case_reset_warning() + if moxunit_util_platform_is_octave() + reason=['resetting the warning state seems not to work ' ... + '(TODO: file a bug report?)']; + moxunit_throw_test_skipped_exception(reason); + return; + end + + s=warning('query'); + state_resetter=onCleanup(@()warning(s)); + + % generate unique id + id=sprintf('%s:%s:%s',randstr(),randstr(),randstr()); + + assertEqual(get_warning_state(id),[]) + + name=randstr(); + location=randstr(); + func=@()warning('off',id); + f=MOxUnitFunctionHandleTestCase(name, location, func); + rep=MOxUnitTestReport(0,1); + run(f,rep); + + assertEqual(get_warning_state(id),[]) + +function s=get_warning_state(id) +% return empty array if warning state not present, or 'on' or 'off' + w=warning('query'); + idx=find(strcmp(id,{w.identifier}))'; + + if isempty(idx) + s=[]; + return; + end + + assert(numel(idx)==1); + s=w(idx).state; + + +function disable_warning(id) + warning('off',id); + +function do_nothing() + % do nothing diff --git a/extern/MOxUnit/tests/test_matlab_unittest_test_wrapper_suite.m b/extern/MOxUnit/tests/test_matlab_unittest_test_wrapper_suite.m new file mode 100644 index 00000000000..c7027bae3c1 --- /dev/null +++ b/extern/MOxUnit/tests/test_matlab_unittest_test_wrapper_suite.m @@ -0,0 +1,123 @@ +function test_suite=test_matlab_unittest_test_wrapper_suite +% tests for MOxUnitFunctionHandleTestCase + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_matlab_unittest_test_test_suite_basics + classname='matlab.unittest.Test'; + if isempty(which(classname)) + reason=sprintf('<%s> class is not available',classname); + moxunit_throw_test_skipped_exception(reason); + return; + end + + if ~moxunit_util_platform_supports('diagnostics_recording_plugin') + reason=sprintf('DiagnosticsRecordingPlugin is not available'); + moxunit_throw_test_skipped_exception(reason); + return; + end + + [fn,is_passing]=helper_build_matlab_unittest_test(); + cleaner=onCleanup(@()delete_path_and_file_and_dir(fn)); + + [pth,nm]=fileparts(fn); + addpath(pth); + + func=str2func(nm); + matlab_test_struct=func(); + + ntests=numel(is_passing); + assertEqual(ntests,numel(matlab_test_struct)); + + % test individual tests + for k=1:ntests + mox_test=MOxUnitMatlabUnitWrapperTestCase(matlab_test_struct(k)); + assertTrue(isa(mox_test,'MOxUnitTestCase')); + assertEqual(getName(mox_test),matlab_test_struct(k).Name); + + report=MOxUnitTestReport(0,1); + report=run(mox_test,report); + assertEqual(countTestOutcomes(report),1); + s=wasSuccessful(report); + assertEqual(s,is_passing(k)); + end + + % error if more than a single test case in wrapper + assertExceptionThrown(@()... + MOxUnitMatlabUnitWrapperTestCase(matlab_test_struct),''); + + % add entire suite + suite=MOxUnitTestSuite(); + suite=addFromFile(suite,fn); + + assertEqual(countTestNodes(suite),ntests); + + report=MOxUnitTestReport(0,1); + report=run(suite,report); + + assertEqual(countTestOutcomes(report),ntests); + + for k=1:ntests + node=getTestNode(suite,k); + assert(isa(node,'MOxUnitMatlabUnitWrapperTestCase')); + + outcome=getTestOutcome(report,k); + s=isSuccess(outcome); + assertEqual(s,is_passing(k)); + end + + + + +function test_matlab_unittest_test_wrapper_exceptions() + aet=@(varargin)assertExceptionThrown(@()... + MOxUnitMatlabUnitWrapperTestCase(varargin{:}),''); + aet('foo'); + aet(struct); + + +function delete_path_and_file_and_dir(fn) + pth=fileparts(fn); + rmpath(pth); + + delete(fn); + rmdir(pth); + +function [fn,is_passing]=helper_build_matlab_unittest_test() + ntests=ceil(rand()*10+10); + + is_passing=false(ntests,1); + lines=cell(ntests,1); + + for k=1:ntests + is_ok=rand()>.5; + + is_passing(k)=is_ok; + + header=sprintf('function test_%d(unused)',k); + if is_ok + line=sprintf('abs(2);'); + else + line='error(''foo'')'; + end + + lines{k}=sprintf('%s\n%s\n\n',header,line); + end + + nm='test_example'; + test_header=sprintf(['function tests=%s\n'... + 'tests=functiontests(localfunctions);\n'],nm); + + subdir=tempname(); + mkdir(subdir); + + fn=fullfile(subdir,[nm '.m']); + fid=fopen(fn,'w'); + closer=onCleanup(@()fclose(fid)); + + fprintf(fid,test_header); + fprintf(fid,'%s',lines{:}); + diff --git a/extern/MOxUnit/tests/test_moxunit_isa_test_skipped_exception.m b/extern/MOxUnit/tests/test_moxunit_isa_test_skipped_exception.m new file mode 100644 index 00000000000..0b815162c66 --- /dev/null +++ b/extern/MOxUnit/tests/test_moxunit_isa_test_skipped_exception.m @@ -0,0 +1,24 @@ +function test_suite=test_moxunit_isa_test_skipped_exception + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_moxunit_isa_test_skipped_exception_basics() + try + moxunit_throw_test_skipped_exception('foo'); + assert(false,'should not come here'); + catch + e=lasterror(); + end + + assert_matches(true,e.identifier); + assert_matches(false,e.identifier(2:end)); + assert_matches(false,'skipped'); + +function assert_matches(tf,x) + e=struct(); + e.identifier=x; + + assertEqual(tf,moxunit_isa_test_skipped_exception(e)) diff --git a/extern/MOxUnit/tests/test_moxunit_runtests.m b/extern/MOxUnit/tests/test_moxunit_runtests.m new file mode 100644 index 00000000000..8c62b02cd90 --- /dev/null +++ b/extern/MOxUnit/tests/test_moxunit_runtests.m @@ -0,0 +1,323 @@ +function test_suite=test_moxunit_runtests + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_moxunit_runtests_basics + slow_flag = ispc() && moxunit_util_platform_is_octave(); + if slow_flag + % Skip if running in octave on windows. From some reason Octave + % chokes heavilly on temporary file access and deletion. + reason = '''test_moxunit_runtests_basics'' is very slow in Octave on Windows!'; + moxunit_throw_test_skipped_exception(reason) + fprintf('This test will take a very long time\n'); + end + passed_cell={'','abs(2);','for k=1:10,2+k;end'}; + failed_cell={'error(''expected'');','[1,2]+[1,2,3];'}; + skipped_cell={'moxunit_throw_test_skipped_exception(''skip'')'}; + + combis=all_binary_combinations(8); + for k=1:size(combis,1) + log_fn=tempname(); + cleanup_helper('add_file',log_fn); + + combi=combis(k,:); + [has_passed,has_failed,has_skipped,... + use_recursion,in_subdirectory,... + has_error,verbose_output,add_suite_instance]=deal(combi{:}); + + + dir_to_test=tempname(); + cleanup_helper('add_directory',dir_to_test); + mkdir_recursively(dir_to_test); + + if in_subdirectory + % put tests in subdirectory. When not using recursive option in + % moxunit_runtests, then no tests are found. + dir_with_tests=tempname(dir_to_test); + cleanup_helper('add_directory',dir_with_tests); + mkdir_recursively(dir_with_tests); + else + dir_with_tests=dir_to_test; + end + + % make directory for tests + n_passed=add_tests(dir_with_tests,has_passed,passed_cell); + n_failed=add_tests(dir_with_tests,has_failed,failed_cell); + n_skipped=add_tests(dir_with_tests,has_skipped,skipped_cell); + + args={'-logfile',log_fn}; + if add_suite_instance + suite=MOxUnitTestSuite(); + suite=addFromDirectory(suite,dir_to_test,... + '.*\.m',use_recursion); + args{end+1}=suite; + else + args{end+1}=dir_to_test; + end + + if has_error + args{end+1}='-foo'; + end + + if verbose_output + args{end+1}='-verbose'; + end + + if use_recursion + args{end+1}='-recursive'; + end + + handle=@()moxunit_runtests(args{:}); + if has_error + assertExceptionThrown(handle,'moxunit:illegalParameter'); + else + result=handle(); + + if in_subdirectory && ~use_recursion + test_stats=[0 0 0]; + must_have_passed=true; + else + test_stats=[n_passed,n_failed,n_skipped]; + must_have_passed=~(has_error || has_failed); + end + + assertEqual(must_have_passed, result); + + test_labels={'.','F','s';... + 'passed','failure','skipped'}; + assert_logfile_matches(log_fn,verbose_output,... + test_stats,test_labels) + + end + cleanup_helper('cleanup'); + if slow_flag + fprintf('Combination test #%d/%d\n',k,size(combis,1)) + end + end + +function test_moxunit_runtests_partitions + + cleaner=onCleanup(@()cleanup_helper('cleanup')); + dir_to_test=tempname(); + mkdir(dir_to_test); + cleanup_helper('add_directory',dir_to_test); + log_fn=tempname(); + cleanup_helper('add_file',log_fn); + + test_partition_count=3+ceil(rand()*5); + ntests_per_partitions=3+ceil(rand()*5); + + ntests=test_partition_count*ntests_per_partitions; + + test_str_cell=cell(ntests,1); + passes=false(ntests,1); + for k=1:ntests + p=rand()>.5; + passes(k)=p; + + if p + test_str='abs(2);'; + else + test_str='error(''foo'')'; + end + + test_str_cell{k}=test_str; + end + + add_tests(dir_to_test,true,test_str_cell); + + + test_labels={'.','F'}; + for test_partition_index=1:test_partition_count + args={dir_to_test,'-logfile',log_fn,... + '-partition_index',test_partition_index,... + '-partition_count',test_partition_count}; + + result=moxunit_runtests(args{:}); + idx=test_partition_index:test_partition_count:ntests; + use_passes=passes(idx); + assertEqual(result,all(use_passes)); + + test_stats=[sum(use_passes), sum(~use_passes), 0]; + assert_logfile_matches(log_fn,false,... + test_stats,test_labels) + end + + +function count=add_tests(test_dir,do_add,cell_with_tests) + count=0; + if do_add + for k=1:numel(cell_with_tests) + idx=cleanup_helper('count'); + + name=sprintf('test_%03d',idx); + fn=fullfile(test_dir,sprintf('%s.m',name)); + cleanup_helper('add_file',fn); + write_test_mfile(fn,name,cell_with_tests{k}); + + count=count+1; + end + end + + +function assert_logfile_matches(log_fn,verbose_output,... + test_stats,test_labels) + fid=fopen(log_fn); + cleaner=onCleanup(@()fclose(fid)); + + content=fread(fid,'char=>char')'; + result_start=strfind(content,sprintf('\n')); + result_end=strfind(content,'------'); + + result=content(result_start(1):result_end(1)); + + labels_row=1; + if verbose_output + labels_row=labels_row+1; + end + + labels=test_labels(labels_row,:); + n_labels=numel(labels); + + for k=1:n_labels + pat=regexptranslate('escape',labels{k}); + count=numel(regexp(result,pat,'start')); + assertEqual(count,test_stats(k)); + end + + +function combis=all_binary_combinations(count) + n=2^(count-1); + first_half=1:n; + second_half=first_half+n; + + combis=cell(n,count); + combis(first_half,1)=repmat({true},1,n); + combis(second_half,1)=repmat({false},1,n); + + if count>1 + next_combi=all_binary_combinations(count-1); + combis(first_half,2:end)=next_combi; + combis(second_half,2:end)=next_combi; + end + + +function write_test_mfile(fn,name,body) + content=sprintf(['function test_suite=%s\n'... + 'try\n'... + ' test_functions=localfunctions();\n',... + 'catch\n',... + 'end\n',... + 'initTestSuite;\n',... + '\n'... + 'function %s_func\n',... + ' %s'],... + name,name,body); + + % make sure directory exists + parent=fileparts(fn); + mkdir_recursively(parent); + + fid=fopen(fn,'w'); + file_closer=onCleanup(@()fclose(fid)); + fprintf(fid,'%s',content); + +function mkdir_recursively(dir_name) + if ~exist(dir_name,'dir') + parent=fileparts(dir_name); + mkdir_recursively(parent); + mkdir(dir_name); + end + + +function c=cleanup_helper(task,varargin) +% cleanup_helper('add_file','foo') +% cleanup_helper('add_directory','bar') +% add the file or directory 'foo' or 'bar' to the list +% of files and directories stored internally +% cleanup_helper('cleanup') removes all internally stores files and +% directory +% + persistent directories; + persistent files; + + c=0; + + switch task + case 'add_file' + if isnumeric(files) + files=cell(0); + end + + files{end+1}=varargin{1}; + + case 'add_directory' + assert(numel(varargin)==1); + if isnumeric(directories) + directories=cell(0); + end + + directories{end+1}=varargin{1}; + + case 'cleanup' + assert(numel(varargin)==0); + if iscell(files) + + if moxunit_util_platform_is_octave() + for k=1:numel(files) + file = files{k}; + if exist(file,'file') + %tic + unlink(file); % This is a bit faster in Octave + %toc + end + end + + else + s = warning('error','MATLAB:DELETE:Permission'); %#ok + c = onCleanup(@() warning(s)); % Reset warning state + + for k=1:numel(files) + file = files{k}; + if exist(file,'file') + try %#ok + delete(file); + end + end + end + end + + files=[]; + end + + if iscell(directories) + if moxunit_util_platform_is_octave() + % GNU Octave requires, by defaualt, confirmation when + % using rmdir - unless confirm_recursive_rmdir is set + % explicitly + % Here the state of confirm_recursive_rmdir is stored, + % and set back to its original value when leaving this + % function. + confirm_val=confirm_recursive_rmdir(false); + cleaner=onCleanup(@()confirm_recursive_rmdir(confirm_val)); + end + + for k=1:numel(directories) + directory=directories{k}; + if moxunit_util_isfolder(directory) + rmdir(directory,'s'); + end + end + + directories=[]; + end + + case 'count' + c=numel(directories)+numel(files); + + otherwise + assert(false,'illegal task') + end diff --git a/extern/MOxUnit/tests/test_moxunit_set_path.m b/extern/MOxUnit/tests/test_moxunit_set_path.m new file mode 100644 index 00000000000..b2a9eb6f446 --- /dev/null +++ b/extern/MOxUnit/tests/test_moxunit_set_path.m @@ -0,0 +1,41 @@ +function test_suite=test_moxunit_set_path + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_moxunit_set_path_root + helper_test_with_subdir(''); + +function test_moxunit_set_path_util + helper_test_with_subdir('util'); + +function helper_test_with_subdir(subdir) + orig_path=path(); + path_resetter=onCleanup(@()path(orig_path)); + + func=@moxunit_set_path; + func_name=func2str(func); + + % remove from path + root_dir=fileparts(which(func_name)); + relative_dir=fullfile(root_dir,subdir); + rmpath(relative_dir); + + % should not be in path + assert(~is_elem(path(),relative_dir,pathsep())); + + % must be now in path + directories_added_cell=func(); + assert(is_elem(path(),relative_dir,pathsep())); + + % directory must have been added and part of the output + assert(numel(directories_added_cell)>0) + directories_added_str=sprintf(['%s' pathsep()],... + directories_added_cell{:}); + assert(is_elem(directories_added_str,relative_dir,pathsep())); + + +function tf=is_elem(haystack, needle, sep) + tf=~isempty(strfind([sep haystack sep], [sep needle sep])); diff --git a/extern/MOxUnit/tests/test_moxunit_util_elem2str.m b/extern/MOxUnit/tests/test_moxunit_util_elem2str.m new file mode 100644 index 00000000000..e67b1121726 --- /dev/null +++ b/extern/MOxUnit/tests/test_moxunit_util_elem2str.m @@ -0,0 +1,168 @@ +function test_suite=test_moxunit_util_elem2str + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_moxunit_util_elem2str_tiny + aeq=@assert_expected_output; + % empty string + aeq('0x0 char (empty)\n''''',''); + + % string in row vector form + aeq('1x3 char\n''abc''','abc'); + + % matrix + aeq('2x3 double\n[4 3 2;3 4 1]',[4 3 2; 3 4 1]); + + % random data + x=randn(2); + precision=5; + aeq(['2x2 double\n' mat2str(x,precision)],x); + +function test_moxunit_util_elem2str_big_matrix + aeq=@assert_expected_output; + + aeq( ['4x4 double\n'... + '[1 -1e-08 -1e-08 -1e-08;'... + '-1e-08 1 -1e-08 -1e-08;'... + '-1e-08 -1e-08 1 -1e-08;'... + '-1e-08 -1e-08 -1e-08 1]'... + ],... + eye(4)-1e-8); + +function test_moxunit_util_elem2str_big_cell + aeq=@assert_expected_output; + + aeq('100x2 cell',cell(100,2)) + +function test_moxunit_util_elem2str_tiny_with_evalc + aeq=@assert_expected_output_evalc_if_present; + + % string with non-row vector form + aeq('2x3 char\nabc\ndef',... + '2x3 char',... + ['abc';'def']); + + % 3D string array + aeq('2x3x2 char\n\n(:,:,1) =\n\nabc\ncde\n\n(:,:,2) =\n\nefg\nghi',... + '2x3x2 char',... + cat(3,['abc';'cde'],['efg';'ghi'])) + + % logical array + aeq('1x2 logical\n 0 1','1x2 logical',[false true]); + + % cell array + aeq({'1x2 cell\n ''foo'' [1]',... + '1x2 cell\n\n{\n[1,1] = foo\n[1,2] = 1\n}',... + '1x2 cell\n {''foo''} {[1]}'},... + '1x2 cell',{'foo',1}); + + +function test_moxunit_util_elem2str_custom_class +% Make a temporary class with a size function that throws an error; +% This tests the functionality of util_elem2str for the case that not +% even a class' 'size' function is available + tmpdir=tempname(tempdir); + cleaner=onCleanup(@()remove_path_directory(tmpdir)); + + classname=sprintf('my_class'); + classdir=fullfile(tmpdir,sprintf('@%s',classname)); + + if ~exist(tmpdir,'dir') + % GNU Octave returns a temporary directory that does not exist; + % so create it first + mkdir(tmpdir); + end + + % make subdirectory for the class + mkdir(classdir); + + write_contents(classdir,classname,... + ['function obj=%s()\n'... + 'obj=class(struct(),''%s'');'],... + classname,classname); + write_contents(classdir,'size',... + ['function size(obj)\n'... + 'error(''raises error'');']); + addpath(tmpdir); + constructor=str2func(classname); + + % make object instance + obj=constructor(); + + aeq=@assert_expected_output; + aeq(sprintf('%s',classname),obj); + +function write_contents(dirname,fname,pat,varargin) +% helper function to write .m file + fn=fullfile(dirname,sprintf('%s.m',fname)); + + fid=fopen(fn,'w'); + cleaner=onCleanup(@()fclose(fid)); + fprintf(fid,pat,varargin{:}); + + +function remove_path_directory(dir_name) + % removes dir_name from the search path and from the file system + + rmpath(dir_name); + if moxunit_util_platform_is_octave() + % GNU Octave requires, by defaualt, confirmation when using rmdir. + % The state of confirm_recursive_rmdir is stored, and set back + % to its original value when leaving this function. + confirm_val=confirm_recursive_rmdir(false); + cleaner=onCleanup(@()confirm_recursive_rmdir(confirm_val)); + end + + rmdir(dir_name,'s'); + + +function assert_expected_output_evalc_if_present(a,b,varargin) + mex_file_code=3; + if exist('evalc','builtin') || ... + exist('evalc')==mex_file_code + to_compare=a; + else + to_compare=b; + end + + assert_expected_output(to_compare,varargin{:}); + + +function assert_expected_output(to_compare,varargin) + result=moxunit_util_elem2str(varargin{:}); + + if islogical(varargin{1}) + assert(ischar(to_compare)); + expected=sprintf(to_compare); + if ~is_equal_modulo_whitespace(result,expected); + assertEqual(to_compare,expected,'Not equal modulo whitespace'); + end + else + if ~iscell(to_compare) + to_compare={to_compare}; + end + + expected_cell=cellfun(@sprintf,to_compare,'UniformOutput',false); + + for k=1:numel(expected_cell) + if is_equal_modulo_whitespace(expected_cell{k},result) + return; + end + end + + msg=sprintf(['Output ''%s'' is not equal modulo whitespace '... + 'to any of: ''%s'''],... + result,... + moxunit_util_strjoin(expected_cell,''', ''')); + error(msg); + end + + +function tf=is_equal_modulo_whitespace(a,b) + simplify_whitespace=@(x)regexprep(x,'\s+',' '); + tf=isequal(simplify_whitespace(a),simplify_whitespace(b)); + + diff --git a/extern/MOxUnit/tests/test_moxunit_util_escape_xml.m b/extern/MOxUnit/tests/test_moxunit_util_escape_xml.m new file mode 100644 index 00000000000..05dcfeeeeb1 --- /dev/null +++ b/extern/MOxUnit/tests/test_moxunit_util_escape_xml.m @@ -0,0 +1,27 @@ +function test_suite=test_moxunit_util_escape_xml + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_moxunit_util_escape_xml_basics + aeq=@(a,b)assertEqual(moxunit_util_escape_xml(a),b); + + aeq('asdk','asdk'); + aeq('&','&'); + aeq('"','"'); + aeq('''','''); + aeq('<','<'); + aeq('>','>'); + + aeq('apos&&&apos','apos&&&apos'); + + aeq('&&','&amp&'); + aeq('"','&quot'); + aeq('&apos','&apos'); + aeq('<','&lt'); + aeq('>','&gt'); + + + diff --git a/extern/MOxUnit/tests/test_moxunit_util_find_files.m b/extern/MOxUnit/tests/test_moxunit_util_find_files.m new file mode 100644 index 00000000000..09bfe1d786c --- /dev/null +++ b/extern/MOxUnit/tests/test_moxunit_util_find_files.m @@ -0,0 +1,147 @@ +function test_suite=test_moxunit_util_find_files() + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function s=randstr(n) + if nargin<1 + n=10; + end + s=char(ceil(rand(1,n)*26+64)); + +function varargout=make_randstrs(suffix) + n=nargout; + varargout=arrayfun(@(unused)[randstr() suffix],ones(1,n),... + 'UniformOutput',false); + + +function test_moxunit_util_find_files_basics() + ext=['.' randstr(1)]; + + while true + [s1,s2,s3]=make_randstrs(''); + [m1,m2,m3,m4,m5,m6]=make_randstrs(ext); + + all_fns={s1,s2,s3,... + m1,m2,m3,m4,m5,m6}; + + all_fns_are_different=numel(unique(all_fns))==numel(all_fns); + if all_fns_are_different + break; + end + end + + to_add={{m1},{m2},... + {s1},{s1,m3},{s1,m4},... + {s1,s2},{s1,s2,m5},... + {s1,s2,s3},{s1,s2,s3,m6}}; + + root_dir=tempname(); + full_paths=make_files(root_dir,to_add,ext); + cleaner=onCleanup(@()remove_paths(full_paths)); + + for is_recursive=[false,true] + for with_pat=[false,true] + if is_recursive + if with_pat + pat=sprintf('(.%s)|(foo)|(%s)',m3(2:end),m6); + expected={{s1,m3},{s1,s2,s3,m6}}; + else + pat='.*'; + msk=cellfun(@(x)strcmp(x{end}(end+[-1,0]),ext),to_add); + expected=to_add(msk); + end + else + if with_pat + pat=sprintf('(%s.)|(%s)',m2(1:(end-1)),m3); + expected={{m2}}; + else + pat='.*'; + expected={{m1},{m2}}; + end + + end + + assert_find_files_matches(root_dir,pat,is_recursive,expected); + end + end + +function assert_find_files_matches(root_dir,pat,is_recursive,expected) + result=moxunit_util_find_files(root_dir,pat,is_recursive); + + n_expected=numel(expected); + + full_expected=cell(n_expected,1); + for k=1:n_expected + full_expected{k}=fullfile(root_dir,expected{k}{:}); + end + + assertEqual(sort(full_expected(:)),sort(result(:))); + + +function [full_paths,is_dir]=make_files(root_dir,to_add,ext) + n_paths=numel(to_add); + + full_paths=cell(1+n_paths,1); + is_dir=false(1+n_paths,1); + + + counter=0; + if ~moxunit_util_isfolder(root_dir) + counter=counter+1; + mkdir(root_dir); + full_paths{counter}=root_dir; + is_dir(counter)=true; + end + + + ext_pat=[regexptranslate('escape',ext) '$']; + + for k=1:n_paths + nm=fullfile(to_add{k}{:}); + + is_file=~isempty(regexp(nm,ext_pat,'once')); + + p=fullfile(root_dir,nm); + + if is_file + write_random_file(p); + else + mkdir(p); + end + + counter=counter+1; + full_paths{counter}=p; + is_dir(counter)=~is_file; + end + +function write_random_file(p) + fid=fopen(p,'w'); + cleaner=onCleanup(@()fclose(fid)); + fprintf(fid,'%s',randstr()); + + +function remove_paths(full_paths) + for k=numel(full_paths):-1:1 + p=full_paths{k}; + + if ~ischar(p) + % not a path, ignore + continue; + end + + if moxunit_util_isfolder(p) + if(numel(dir(p))>2) + error('Directory %s not empty',p); + end + + rmdir(p); + else + delete(p); + end + end + + + diff --git a/extern/MOxUnit/tests/test_moxunit_util_get_test_name_regexp.m b/extern/MOxUnit/tests/test_moxunit_util_get_test_name_regexp.m new file mode 100644 index 00000000000..2c417b8e0a4 --- /dev/null +++ b/extern/MOxUnit/tests/test_moxunit_util_get_test_name_regexp.m @@ -0,0 +1,29 @@ +function test_suite=test_moxunit_util_get_test_name_regexp() + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_moxunit_util_get_test_name_basics() + matches={'test_foo','TestFoo','FooTest','foo_test'}; + non_matches={'te_st','foo','bar','','foo_test_bar','fTestb'}; + + pat=moxunit_util_get_test_name_regexp(); + assert_matches_is(matches,pat,true); + assert_matches_is(non_matches,pat,false); + + +function test_moxunit_util_get_test_name_ends_with_dollar_sign() + pat=moxunit_util_get_test_name_regexp(); + assertEqual(sum(pat=='$'),1); + assertTrue(pat(end)=='$'); + + +function assert_matches_is(inputs,pat,expected) + for k=1:numel(inputs) + m=regexp(inputs{k},pat,'once'); + + assertEqual(~isempty(m),expected,... + sprintf('fail for %s',inputs{k})); + end diff --git a/extern/MOxUnit/tests/test_moxunit_util_input2str.m b/extern/MOxUnit/tests/test_moxunit_util_input2str.m new file mode 100644 index 00000000000..ebf4cf978d8 --- /dev/null +++ b/extern/MOxUnit/tests/test_moxunit_util_input2str.m @@ -0,0 +1,28 @@ +function test_suite=test_moxunit_util_input2str + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + + +function test_moxunit_util_input2str_basics + aeq=@(expected,varargin)assertEqual(sprintf(expected{:}),... + moxunit_util_input2str(varargin{:})); + w=randstr(); + x=randstr(); + y=randstr(); + z=randstr(); + ys=moxunit_util_elem2str(y); + zs=moxunit_util_elem2str(z); + + aeq({'%s\n%s\n',w,x},w,x); + aeq({'%s\n%s\n\nInput: %s\n',w,x,ys},w,x,y); + aeq({'%s\n%s\n\nFirst input: %s\n\nSecond input: %s\n',... + w,x,ys,zs},w,x,y,z); + + +function s=randstr() + s=char(rand(1,10)*24+65); + + diff --git a/extern/MOxUnit/tests/test_moxunit_util_is_message_identifier.m b/extern/MOxUnit/tests/test_moxunit_util_is_message_identifier.m new file mode 100644 index 00000000000..e8c02896ccf --- /dev/null +++ b/extern/MOxUnit/tests/test_moxunit_util_is_message_identifier.m @@ -0,0 +1,66 @@ +function test_suite=test_moxunit_util_is_message_identifier + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_moxunit_util_is_message_identifier_non_char + aet=@(varargin)assertExceptionThrown(@()... + moxunit_util_is_message_identifier(varargin{:}),''); + + aet(struct()); % struct + aet({'id:id2'}); % cell + aet(2); % numeric + aet(false); % boolean + aet(true); % boolean + aet([]); % boolean + +function test_moxunit_util_is_message_identifier_true + true_strs={'a:b',... + 'a___:b___',... + 'abc:def:ghi:jkl:mno',... + 'a_b:c_123',... + }; + + for k=1:numel(true_strs) + s=true_strs{k}; + assertTrue(moxunit_util_is_message_identifier(s),s); + end + + +function test_test_moxunit_util_is_message_identifier_false + false_strs={'',... % empty string + 'a',... % missing colon + 'ab',... % missing colon + 'ab: cd',... % contains space + sprintf('ab:c\td'),... % contains tabs + '_a:b',... % starts with underscore + 'a:_b',... % starts with underscore + '1ab:def',... % has a number + ':abc:def:',... % starts with colon + 'abc:def:',... % ends with colon + 'a-b',... % no colon + }; + + + for k=1:numel(false_strs) + s=false_strs{k}; + + assertFalse(moxunit_util_is_message_identifier(s),s); + end + +function test_test_moxunit_util_is_message_identifier_octave + % in octave, the '-' is allowed to be in message identifers; + % in Matlab this is not allowed. + % For compatibility we allow the '-' to be allowed in message + % identifiers + true_strs={'a-b:b:cdef-ghij',... + 'a-b:c',... + 'a:b-c',... + }; + + for k=1:numel(true_strs) + s=true_strs{k}; + assertTrue(moxunit_util_is_message_identifier(s),s); + end diff --git a/extern/MOxUnit/tests/test_moxunit_util_isfolder.m b/extern/MOxUnit/tests/test_moxunit_util_isfolder.m new file mode 100644 index 00000000000..0f839ab1409 --- /dev/null +++ b/extern/MOxUnit/tests/test_moxunit_util_isfolder.m @@ -0,0 +1,24 @@ +function test_suite=test_moxunit_util_isfolder + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + + +function test_moxunit_util_isfolder_basics() + % current direcoty + assertTrue(moxunit_util_isfolder(pwd)); + assertTrue(moxunit_util_isfolder('.')); + assertTrue(moxunit_util_isfolder('..')); + + % this test fails almost surely + r = rand_str(); + assertFalse(moxunit_util_isfolder(r)); + + % illegal input type + assertExceptionThrown(@()moxunit_util_isfolder(struct)); + +function s=rand_str() + n = 25; + s=char(26*rand(1,n)+65); diff --git a/extern/MOxUnit/tests/test_moxunit_util_mfile_subfunctions.m b/extern/MOxUnit/tests/test_moxunit_util_mfile_subfunctions.m new file mode 100644 index 00000000000..c7c87108f57 --- /dev/null +++ b/extern/MOxUnit/tests/test_moxunit_util_mfile_subfunctions.m @@ -0,0 +1,191 @@ +function test_suite=test_moxunit_util_mfile_subfunctions + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function s=rand_str() + s=char(20*rand(1,10)+65); + +function test_multiple_subfunctions + % variable number of functions in body + for func_count=0:5 + func_names=cell(1,func_count); + lines=cell(func_count*2,1); + + for k=1:func_count + func_name=rand_str(); + func_names{k}=func_name; + + lines{k*2-1}=[rand_str() rand_str()]; + lines{k*2}=sprintf('function %s',func_name); + end + + n_out=zeros(1,func_count); + helper_test_with_lines(func_names, n_out, lines); + end + +function test_multiple_subfunctions_nout + % variable number of functions in body, each with variable number of + % output arguments + argouts={{'',' '},... + {'~','[~]','a','aa','[a]',' [ aabb ] '},... + {'[~, aa]','[~ ~]','[a bb ]','[a,bb]','[ ab , ba ]'},... + {'[~,~,~]',' [ a, bb, c ]','[ a,~,b ]'},... + }; + + for func_count=0:10 + func_names=cell(1,func_count); + lines=cell(func_count*2,1); + + n_out=zeros(func_count,1); + for k=1:func_count + func_name=rand_str(); + func_names{k}=func_name; + + n_out_func_plus_one=ceil(rand()*numel(argouts)); + argout_cell=argouts{n_out_func_plus_one}; + argout_str=argout_cell{ceil(rand()*numel(argout_cell))}; + + if n_out_func_plus_one>1 + argout_str=sprintf('%s = ',argout_str); + end + + lines{k*2-1}=[rand_str() rand_str()]; + lines{k*2}=sprintf('function %s %s',argout_str,func_name); + + n_out(k)=n_out_func_plus_one-1; + end + + helper_test_with_lines(func_names, n_out, lines); + end + + +function test_commented_subfunction() + helper_test_with_lines({},[],{',','% function foo',''}); + +function test_in_string_subfunction() + helper_test_with_lines({},[],{'','''function foo''',''}); + +function test_no_whitespace_subfunction() + helper_test_with_lines({},[],{'functionfoo'}); + +function test_newline_subfunction() + helper_test_with_lines({'foo'},0,{sprintf('function ...\nfoo')}); + +function test_no_whitespace_newline_subfunction() + helper_test_with_lines({},0,{sprintf('function...\nfoo')}); + +function test_tilde_newline_subfunction() + helper_test_with_lines({'foo'},1,{sprintf('function ~=foo')}); + +function test_multiple_tilde_newline_subfunction() + helper_test_with_lines({'foo'},2,{sprintf('function[~,~]=foo')}); + +function test_no_space_subfunction_long_vars + helper_test_with_lines({'foo'},1,{sprintf('function aa=foo(bb, cc)')}); + +function test_no_space_subfunction_short_vars + helper_test_with_lines({'foo'},1,{sprintf('function a=foo(b, c)')}); + +function test_different_args_subfunction + % test with various types of whitespace, number of input arguments, and + % number of output arguments + + slow_flag = ispc() && moxunit_util_platform_is_octave(); + if slow_flag + % Skip if running in octave on windows. From some reason Octave + % chokes heavilly on temporary file access and deletion. + reason = '''test_different_args_subfunction'' is very slow in Octave on Windows!'; + moxunit_throw_test_skipped_exception(reason) + fprintf('This test will take a very long time\n'); + end + + whitespace_cell={' ',... + ' ',... + sprintf('\t'),... + sprintf(' ...\n'),... + sprintf(' ...\r\n')}; + for i_sp=1:numel(whitespace_cell) + whitespace=whitespace_cell{i_sp}; + for n_out=-1:3 + arg_out=helper_build_arg_list(n_out,whitespace,'[',']','='); + for n_in=-1:3 + arg_in=helper_build_arg_list(n_in,whitespace,'(',')',''); + func_name=rand_str(); + parts={'function',arg_out,func_name,arg_in}; + line=moxunit_util_strjoin(parts,whitespace); + helper_test_with_lines({func_name},max(n_out,0),{line}); + if slow_flag + fprintf('i_sp: %0f, n_out: %0f, n_in: %0f\n',i_sp,n_out,n_in) + end + end + end + end + +function arg_list=helper_build_arg_list(n_args, whitespace, ... + left_paren, right_paren, suffix) + if n_args<0 + arg_list=''; + else + param_names=arrayfun(@(unused)rand_str,ones(1,n_args),... + 'UniformOUtput',false); + delim=[whitespace ',' whitespace]; + args=moxunit_util_strjoin(param_names,delim); + arg_list_cell={left_paren,args,right_paren,suffix}; + arg_list=moxunit_util_strjoin(arg_list_cell, whitespace); + end + + +function helper_test_with_lines(func_names, n_out, lines) +% func_names is expected cell output from +% moxunit_util_mfile_subfunctions +% when applied to a file containing the 'lines' data +% +% nout is a struct with a field nargout containing +% the number of output arguments for each function + assert(iscell(func_names)); + assert(iscell(lines)); + + tmp_fn=tempname(); + if moxunit_util_platform_is_octave + cleaner=onCleanup(@()unlink(tmp_fn)); % Faster in Octave + else + cleaner=onCleanup(@()delete(tmp_fn)); + end + + % try different line endings + line_ending_cell={'\r\n',... % MS Windows + '\n'}; % Unix-like / OSX + + header_to_ignore=['function ' rand_str]; + lines_with_header=[{header_to_ignore}; lines(:)]; + + for k=1:numel(line_ending_cell) + write_text_file(tmp_fn,lines_with_header,line_ending_cell{k}); + fs=moxunit_util_mfile_subfunctions(tmp_fn); + + % must be a struct + assert(isstruct(fs)); + + % number of functions should match + n_func=numel(func_names); + assertEqual(n_func,numel(fs)); + + % verify content of each element + for j=1:n_func + f=fs(j); + assert(isfield(f,'name')); + assertEqual(func_names{j},f.name); + + assert(isfield(f,'nargout')); + assertEqual(n_out(j),f.nargout); + end + end + + +function fn=write_text_file(fn, lines, line_ending) + fid=fopen(fn,'w'); + fprintf(fid,['%s' line_ending],lines{:}); + fclose(fid); diff --git a/extern/MOxUnit/tests/test_moxunit_util_platform_supports.m b/extern/MOxUnit/tests/test_moxunit_util_platform_supports.m new file mode 100644 index 00000000000..1092548f74a --- /dev/null +++ b/extern/MOxUnit/tests/test_moxunit_util_platform_supports.m @@ -0,0 +1,48 @@ +function test_suite=test_moxunit_util_platform_supports + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_moxunit_util_platform_supports_localfunctions_in_script + flag=moxunit_util_platform_supports('localfunctions_in_script'); + + if moxunit_util_platform_is_octave() + v=moxunit_util_platform_version(); + expected_flag=version_less_than(v,[6,0]); + else + v=moxunit_util_platform_version(); + expected_flag=version_less_than(v,[9,0]); + end + + assertEqual(flag,expected_flag); + + +function test_moxunit_util_platform_supports_diagnostics_recording_plg + flag=moxunit_util_platform_supports('diagnostics_recording_plugin'); + + if moxunit_util_platform_is_octave() + expected_flag=false; + else + s='matlab.unittest.plugins.DiagnosticsRecordingPlugin'; + expected_flag=~isempty(which(s)); + end + + assertEqual(flag,expected_flag); + + +function test_moxunit_util_platform_supports_exceptions + aet=@(varargin)assertExceptionThrown(@()... + moxunit_util_platform_supports(varargin{:}),''); + aet('unknown'); + aet(2); + +function tf=version_less_than(x,y) + tf=false; + for k=1:numel(y) + if x(k)= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + + +function test_platform_version + % get start of version + v=moxunit_util_platform_version(); + assert(isnumeric(v)); + assert(numel(v)>=2); + version_start=sprintf('%d.%d',v(1:2)); + + % now compare with output from 'ver' + if moxunit_util_platform_is_octave() + name='Octave'; + else + name='MATLAB'; + end + + version_struct=ver(name); + version_s=version_struct.Version; + n=numel(version_start); + + assertEqual(version_s(1:n),version_start); + + +function v=dots_to_vec(s) + parts=regexp(s,'\.','split'); + v=cellfun(@str2num,parts); + + + diff --git a/extern/MOxUnit/tests/test_moxunit_util_regexp_matches.m b/extern/MOxUnit/tests/test_moxunit_util_regexp_matches.m new file mode 100644 index 00000000000..00570600696 --- /dev/null +++ b/extern/MOxUnit/tests/test_moxunit_util_regexp_matches.m @@ -0,0 +1,63 @@ +function test_suite=test_moxunit_util_regexp_matches() + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function c=randchar(length) + c=char(ceil(rand(1,length)*26+64)); + +function test_moxunit_util_regexp_matches_basics + for needle_length=1:3 + + % make string to match + needle=randchar(needle_length); + assert_regexp_matches(needle,needle) + + for pat_length=1:4 + % make pattern that matches or does not match needle + pat=randchar(pat_length); + assert_regexp_matches([needle pat],needle) + assert_regexp_matches([pat needle],needle) + + if needle_length==pat_length + matcher=@assert_regexp_matches; + else + matcher=@assert_regexp_not_matches; + end + matcher(needle,sprintf('^%s$',... + repmat('.',1,pat_length))) + + + while true + different_haystack=randchar(pat_length); + match=bsxfun(@eq,different_haystack,needle'); + if all(~match(:)) + break; + end + end + + assert_regexp_not_matches(needle,different_haystack) + end + end + +function test_moxunit_util_regexp_matches_exceptions() + aet=@(varargin)assertExceptionThrown(@()... + moxunit_util_regexp_matches(varargin{:}),''); + bad_inputs={{[],''},... % first argument not a string + {'',[]},... % second argument not a string + }; + for k=1:numel(bad_inputs) + args=bad_inputs{k}; + aet(args{:}); + end + + +function assert_regexp_matches(needle, pat) + assertTrue(moxunit_util_regexp_matches(needle,pat)); + +function assert_regexp_not_matches(needle, pat) + assertFalse(moxunit_util_regexp_matches(needle,pat)); + + diff --git a/extern/MOxUnit/tests/test_moxunit_util_remove_matlab_anchor_tag.m b/extern/MOxUnit/tests/test_moxunit_util_remove_matlab_anchor_tag.m new file mode 100644 index 00000000000..7c1af0bb689 --- /dev/null +++ b/extern/MOxUnit/tests/test_moxunit_util_remove_matlab_anchor_tag.m @@ -0,0 +1,45 @@ +function test_suite=test_moxunit_util_remove_matlab_anchor_tag + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_moxunit_util_remove_matlab_anchor_tag_basics + rand_str=@()char(20*rand(1,10)+65); + + message=rand_str(); + file_name=sprintf('%s',rand_str()); + function_name=rand_str(); + file_path=fullfile(rand_str(),rand_str(),sprintf('%s.m',file_name)); + line_number=ceil(rand()*100); + + + in_out1={sprintf([... + '%s'... + '%s>%s'],... + message,file_name,function_name,file_path,line_number,... + file_name,function_name)... + sprintf('%s%s>%s',... + message,file_name,function_name)}; + + in_out2={sprintf([... + ''... + 'line %d'],... + file_path,line_number,line_number),... + sprintf('line %d',line_number)}; + + in_out3={moxunit_util_strjoin([in_out1(1),in_out2(1)],''),... + moxunit_util_strjoin([in_out1(2),in_out2(2)],'')}; + + in_out_cell=cat(1,in_out1,in_out2,in_out3); + for k=3:size(in_out_cell,1) + a=in_out_cell{k,1}; + b=in_out_cell{k,2}; + + result=moxunit_util_remove_matlab_anchor_tag(a); + assertEqual(result,b); + end diff --git a/extern/MOxUnit/tests/test_moxunit_util_stack2str.m b/extern/MOxUnit/tests/test_moxunit_util_stack2str.m new file mode 100644 index 00000000000..16dbff749b6 --- /dev/null +++ b/extern/MOxUnit/tests/test_moxunit_util_stack2str.m @@ -0,0 +1,94 @@ +function test_suite=test_moxunit_util_stack2str + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_moxunit_util_stack2str_basics + + prefixes={[],'',randstr(),'% %d %s %%'}; + for k=1:numel(prefixes) + n_elem=ceil(rand()*10+10); + + values=cell(3,n_elem); % file, name, line + for j=1:n_elem + values{1,j}=randstr(); + values{2,j}=randstr(); + values{3,j}=ceil(rand()*100); + end + + keys={'file','name','line'}; + stack_arg_cell=arrayfun(@(i) {keys{i};values(i,:)},1:numel(keys),... + 'UniformOutput',false); + stack_arg=cat(1,stack_arg_cell{:}); + stack=struct(stack_arg{:}); + + prefix=prefixes{k}; + if isequal(prefix,[]) + use_prefix=''; + prefix_arg={}; + else + prefix_arg={prefix}; + use_prefix=prefix; + end + + result=moxunit_util_stack2str(stack,prefix_arg{:}); + lines=regexp(result,sprintf('\n'),'split'); + assertEqual(numel(lines),n_elem); + + % compare other lines + show_stack_elem=@(i)sprintf('%s%s:%d (%s)', ... + use_prefix, stack(i).name, ... + stack(i).line, stack(i).file); + expected_lines=arrayfun(show_stack_elem,1:n_elem,... + 'UniformOutput',false); + assertEqual(lines,expected_lines); + + end + + + +function s=randstr() + s=char(rand(1,10)*24+65); + +function test_moxunit_util_stack2str_exceptions + aet=@(varargin)assertExceptionThrown(@()... + moxunit_util_stack2str(varargin{:}),''); + + % first argument must be struct + aet('foo') + aet([]); + aet({}) + + % second argument must be a string + aet(struct(),{}); + aet(struct(),[]); + aet(struct(),struct()); + + % bad input, missing fields + s_missing_field=struct(); + s_missing_field.file='foo'; + aet(s_missing_field); + + % should be ok + s_octave=struct(); + s_octave.file='foo'; + s_octave.name='bar'; + s_octave.line=23; + s_octave.column=2; + s_octave.scope=3; + s_octave.context=[]; + moxunit_util_stack2str(s_octave); + + + +function str=stack2str(stack) + n_stack=numel(stack); + lines=cell(1,n_stack); + for k=1:n_stack + s=stack(k); + lines{k}=sprintf(' %s:%d (%s)', ... + s.name, s.line, s.file); + end + str=moxunit_util_strjoin(lines,'\n'); \ No newline at end of file diff --git a/extern/MOxUnit/tests/test_moxunit_util_strjoin.m b/extern/MOxUnit/tests/test_moxunit_util_strjoin.m new file mode 100644 index 00000000000..2309072f9ab --- /dev/null +++ b/extern/MOxUnit/tests/test_moxunit_util_strjoin.m @@ -0,0 +1,35 @@ +function test_suite = test_moxunit_util_strjoin + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_strjoin_basics + aeq=@(a,varargin)assertEqual(moxunit_util_strjoin(varargin{:}),a); + + aeq('a b cc',{'a','b','cc'}); + aeq('a>###<'); + aeq(sprintf('a\tb\tcc'),{'a','b','cc'}, '\t'); + aeq('a\b\cc',{'a','b','cc'}, '\\'); + aeq('a+b=cc',{'a','b','cc'}, {'+','='}); + +function test_strjoin_exceptions + aet=@(varargin)assertExceptionThrown(@()... + moxunit_util_strjoin(varargin{:}),''); + + % first argument must be cell with strings + aet('a'); + aet(struct) + aet(2); + aet([]); + aet({2}); + + % second argument must be string or cell with strings + aet({'a','b','c'},2); + aet({'a','b','c'},struct); + aet({'a','b','c'},[]); + aet({'a','b','c'},[]); + aet({'a','b','c'},{'x'}); + aet({'a','b','c'},{1,2}); + diff --git a/extern/MOxUnit/tests/test_test_case.m b/extern/MOxUnit/tests/test_test_case.m new file mode 100644 index 00000000000..537272ba724 --- /dev/null +++ b/extern/MOxUnit/tests/test_test_case.m @@ -0,0 +1,22 @@ +function test_suite=test_test_case + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_test_case_basics + rand_str=@()char(20*rand(1,10)+65); + + name=rand_str(); + location=rand_str(); + case_=MOxUnitTestCase(name,location); + + assertEqual(location,getLocation(case_)); + assertEqual(name,getName(case_)); + + assert(~isempty(strfind(str(case_),'MOxUnitTestCase'))); + +function test_test_case_run + case_=MOxUnitTestCase('foo','bar'); + assertExceptionThrown(@()run(case_),'moxunit:notImplemented'); \ No newline at end of file diff --git a/extern/MOxUnit/tests/test_test_node.m b/extern/MOxUnit/tests/test_test_node.m new file mode 100644 index 00000000000..6a76c1b1c6b --- /dev/null +++ b/extern/MOxUnit/tests/test_test_node.m @@ -0,0 +1,16 @@ +function test_suite=test_test_node + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_test_node_basics + rand_str=@()char(20*rand(1,10)+65); + + name=rand_str(); + nd=MOxUnitTestNode(name); + + assertEqual(getName(nd),name); + assertFalse(isempty(strfind(str(nd), 'MOxUnitTestNode'))); + diff --git a/extern/MOxUnit/tests/test_test_outcome.m b/extern/MOxUnit/tests/test_test_outcome.m new file mode 100644 index 00000000000..df48c4d79f8 --- /dev/null +++ b/extern/MOxUnit/tests/test_test_outcome.m @@ -0,0 +1,108 @@ +function test_suite=test_test_outcome + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_test_outcome_abstract_class + test_=rand(3); + duration=rand(1); + outcome=MOxUnitTestOutcome(test_,duration); + + assertEqual(test_,getTest(outcome)); + assertEqual(duration,getDuration(outcome)); + + if moxunit_util_platform_is_octave + undef_err_id='Octave:undefined-function'; + else + undef_err_id='MATLAB:UndefinedFunction'; + end + + assertExceptionThrown(@()getSummaryStr(outcome,'text'),undef_err_id); + assertExceptionThrown(@()getSummaryStr(outcome,'xml'),undef_err_id); + assertExceptionThrown(@()getProgressStr(outcome,'text'),undef_err_id); + +function test_test_passed_outcome + helper_test_outcome(@MOxUnitPassedTestOutcome,{},... + [],'',... + true,true,... + {'','.','passed'}); + +function test_test_skipped_outcome + reason=rand_str(); + + helper_test_outcome(@MOxUnitSkippedTestOutcome,{reason},... + reason,['skipped: ' reason],... + false,true,... + {'','s','skipped'}); + +function test_test_error_outcome + [error_,msg]=rand_error_struct_and_msg(); + + helper_test_outcome(@MOxUnitErroredTestOutcome,{error_},... + error_,['error: ' msg],... + false,false,... + {'','E','error'}); + +function test_test_failure_outcome + [error_,msg]=rand_error_struct_and_msg(); + + helper_test_outcome(@MOxUnitFailedTestOutcome,{error_},... + error_,['failure: ' msg],... + false,false,... + {'','F','failure'}); + +function [error_,msg_text, msg_xml_cell]=rand_error_struct_and_msg() + error_=struct(); + error_.message=rand_str(); + error_.identifier=rand_str(); + + stack=struct(); + stack.name=rand_str(); + stack.line=round(rand()*10); + stack.file=rand_str(); + error_.stack=stack; + + msg_text=sprintf('%s\n %s:%d (%s)',... + error_.message,... + stack.name, stack.line, stack.file); + + +function helper_test_outcome(class_,args,... + content,summary_text,... + is_success,is_non_failure,... + outcome_cell) + + name=rand_str(); + test_=MOxUnitFunctionHandleTestCase(name,rand_str,@abs); + duration=rand(1); + c=class_(test_,duration,args{:}); + assertEqual(getSummaryContent(c),content); + assertEqual(getSummaryStr(c,'text'),summary_text); + + str_xml=getSummaryStr(c,'xml'); + xml_sub_str=sprintf('name="%s"',name); + assert_contains(str_xml, xml_sub_str); + if ~is_non_failure + % must have failed + xml_sub_str=sprintf('<%s message=',outcome_cell{3}); + assert_contains(str_xml,xml_sub_str); + elseif ~is_success + xml_sub_str=sprintf('<%s',outcome_cell{3}); + assert_contains(str_xml,xml_sub_str); + end + + assertEqual(isSuccess(c),is_success); + assertEqual(isNonFailure(c),is_non_failure); + + for verbosity=1:3 + assertEqual(getOutcomeStr(c,verbosity-1),outcome_cell{verbosity}); + end + +function s=rand_str() + s=char(20*rand(1,10)+65); + +function assert_contains(a,b) + assert(~isempty(strfind(a,b))) + diff --git a/extern/MOxUnit/tests/test_test_report.m b/extern/MOxUnit/tests/test_test_report.m new file mode 100644 index 00000000000..d1740d14699 --- /dev/null +++ b/extern/MOxUnit/tests/test_test_report.m @@ -0,0 +1,269 @@ +function test_suite=test_test_report + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function s=randstr() + s=char(20*rand(1,10)+65); + +function test_test_report_output + test_outcomes={{ @MOxUnitPassedTestOutcome},... + {@MOxUnitSkippedTestOutcome,randstr()},... + {@MOxUnitFailedTestOutcome,randstr()},... + {@MOxUnitErroredTestOutcome,randstr()}}; + + for verbosity=0:2 + for report_test=[false,true]; + % open temporary output file + temp_fn=tempname(); + fid=fopen(temp_fn,'w'); + file_closer=onCleanup(@()fclose(fid)); + + % empty report + rep=MOxUnitTestReport(verbosity,fid); + assertEqual(0,countTestOutcomes(rep)); + assertTrue(wasSuccessful(rep)); + + % add tests + test_count=ceil(rand()*5+20); + test_idxs=ceil(rand(test_count,1)*numel(test_outcomes)); + all_passed=true; + + outcome_str_cell=cell(test_count,1); + test_stats=struct(); + assertEqual(test_stats,getTestOutputStatistics(rep)); + + for k=1:test_count + test_idx=test_idxs(k); + + % add test outcome + outcome_cell=test_outcomes{test_idx}; + outcome_constructor=outcome_cell{1}; + outcome_args=outcome_cell(2:end); + + name=randstr(); + location=randstr(); + test_=MOxUnitFunctionHandleTestCase(name,location,'foo'); + duration=rand()*1e3; + test_outcome=outcome_constructor(test_,duration,... + outcome_args{:}); + + is_non_failure=(sum(strcmp(class(test_outcome),... + {'MOxUnitPassedTestOutcome',... + 'MOxUnitSkippedTestOutcome'}))>=1); + + % either report the test (which also adds it), or + % only add the test (without reporting) + if report_test + rep=reportTestOutcome(rep,test_outcome); + else + rep=addTestOutcome(rep,test_outcome); + end + + % verify number of tests + assert(countTestOutcomes(rep)==k); + all_passed=all_passed && is_non_failure; + + % very that wasSuccessful works properly + assertEqual(all_passed,wasSuccessful(rep)); + + % expected output from this test + if report_test + outcome_str=getOutcomeStr(test_outcome,verbosity); + if verbosity==2 + outcome_str=sprintf(' %s in %4.2f s: %s: %s ',... + outcome_str,duration,... + name,location); + end + else + outcome_str=''; + end + + % check getTestOutputStatistics + label=getOutcomeStr(test_outcome,2); + + if ~isfield(test_stats,label) + test_stats.(label)=0; + end + test_stats.(label)=test_stats.(label)+1; + + assertEqual(test_stats,getTestOutputStatistics(rep)); + + outcome_str_cell{k}=outcome_str; + end + + % close temporary file + clear file_closer; + + % read from temporary file + fid=fopen(temp_fn); + file_closer=onCleanup(@()fclose(fid)); + data=fread(fid,inf,'char=>char')'; + clear file_closer; + + % test that all elements are present + expected_data=moxunit_util_strjoin(outcome_str_cell,''); + assert_equal_modulo_whitespace(data, expected_data); + end + end + +function test_test_report_duration + rep=MOxUnitTestReport(0,1); + + assertEqual(getDuration(rep),0); + + duration=rand(); + outcome=MOxUnitPassedTestOutcome(1,duration); + + for k=1:3 + rep=addTestOutcome(rep,outcome); + assertElementsAlmostEqual(getDuration(rep),k*duration); + end + + + +function test_test_report_name + % default name + rep=MOxUnitTestReport(0,1); + assertEqual('MOxUnitTestReport',getName(rep)); + + name=randstr(); + rep=MOxUnitTestReport(0,1,name); + assertEqual(name,getName(rep)); + +function test_test_report_verbosity() + stream=1; + for verbosity=0:2 + rep=MOxUnitTestReport(verbosity,stream); + assertEqual(getVerbosity(rep),verbosity); + end + +function test_test_report_stream() + verbosity=1; + for stream=[1 2] + rep=MOxUnitTestReport(verbosity,stream); + assertEqual(getStream(rep),stream); + end + + +function test_test_report_get_statistics_str_xml + helper_test_get_statistics_str('xml'); + +function test_test_report_get_statistics_str_text + helper_test_get_statistics_str('text'); + +function helper_test_get_statistics_str(format) + + a_test=MOxUnitFunctionHandleTestCase(randstr,randstr,@abs); + outcomes.passed=MOxUnitPassedTestOutcome(a_test,rand()); + outcomes.skipped=MOxUnitSkippedTestOutcome(a_test,rand(),'foo'); + outcomes.failure=MOxUnitFailedTestOutcome(a_test,rand(),struct()); + outcomes.error=MOxUnitErroredTestOutcome(a_test,rand(),struct()); + + skip_pos=2; + failure_pos=3; + + keys=fieldnames(outcomes); + n_keys=numel(keys); + + + for repeat=1:5 + rep=MOxUnitTestReport(0,1); + counts=zeros(n_keys,1); + + for k=1:6 + idx=ceil(rand()*n_keys); + key=keys{idx}; + + outcome=outcomes.(key); + rep=addTestOutcome(rep,outcome); + counts(idx)=counts(idx)+1; + + s=getStatisticsStr(rep,format); + + switch format + case 'xml' + assertEqual('',s); + + case 'text' + has_ok=all(counts(failure_pos:n_keys)==0); + + assertEqual(has_ok,contains(s,'OK')); + assertEqual(~has_ok,contains(s,'FAILED')); + + for j=skip_pos:n_keys + infix=sprintf('%s=%d',keys{j},counts(j)); + assertEqual(contains(s,infix),counts(j)>0); + end + + otherwise + assert(false); + end + end + end + + +function tf=contains(haystack,needle) + tf=~isempty(strfind(haystack,needle)); + +function assert_contains(haystack,needle) + assertTrue(contains(haystack,needle)); + +function test_write_xml_report() + a_test=MOxUnitFunctionHandleTestCase(randstr,randstr,@abs); + err=struct(); + err.message=randstr(); + err.stack=struct('file',randstr(),'line',rand(),'name',randstr()); + outcomes=struct(); + outcomes.passed=MOxUnitPassedTestOutcome(a_test,rand()); + outcomes.failure=MOxUnitFailedTestOutcome(a_test,rand(),err); + + keys=fieldnames(outcomes); + for k=1:numel(keys) + key=keys{k}; + outcome=outcomes.(key); + + rep=MOxUnitTestReport(0,1); + rep=addTestOutcome(rep,outcome); + + + fn=tempname(); + writeXML(rep,fn); + + fid=fopen(fn); + content=fread(fid,inf,'*char')'; + file_closer=onCleanup(@()fclose(fid)); + + + assert_contains(content,'tests="1"'); + passed=wasSuccessful(rep); + if passed + assert_contains(content,'failures="0"') + assert_contains(content,'errors="0"'); + else + assert_contains(content,'failures="1"'); + assert_contains(content,'errors="0"'); + end + + assert_contains(content,''); + assert_contains(content,''); + + clear file_closer + end + + +function assert_equal_modulo_whitespace(a,b) + % In GNU Octave, strings may both be empty but of different size + % (such as [1,0] and [0,0]). Such cases should not fail the test. + both_empty=isempty(a) && isempty(b); + strsplit_by_whitespace=@(x)regexp(x,'\s+','split'); + if ~both_empty + assertEqual(strsplit_by_whitespace(a),strsplit_by_whitespace(b)); + end + + + + diff --git a/extern/MOxUnit/tests/test_test_suite.m b/extern/MOxUnit/tests/test_test_suite.m new file mode 100644 index 00000000000..a4f8ce12eeb --- /dev/null +++ b/extern/MOxUnit/tests/test_test_suite.m @@ -0,0 +1,147 @@ +function test_suite=test_test_suite + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_test_suite_default_name() + suite=MOxUnitTestSuite(); + assertEqual('MOxUnitTestSuite',getName(suite)); + +function test_test_suite_name() + name=rand_str(); + suite=MOxUnitTestSuite(name); + + assertEqual(name,getName(suite)); + +function test_test_suite_tests() + suite=MOxUnitTestSuite(); + + assertEqual(0,countTestNodes(suite)); + + n_nodes=ceil(rand()*5+20); + for k=1:n_nodes + nd=MOxUnitTestNode(rand_str()); + + suite=addTest(suite,nd); + assertEqual(k,countTestNodes(suite)) + nd_again=getTestNode(suite,k); + assertEqual(nd,nd_again); + end + + s_double=addFromSuite(suite,suite); + assertEqual(2*n_nodes,countTestNodes(s_double)); + + +function test_test_suite_set_test() + suite=MOxUnitTestSuite(); + nd=MOxUnitTestNode(rand_str()); + suite=addTest(suite,nd); + + assertEqual(1,countTestNodes(suite)) + + replacements={MOxUnitTestNode(['b' rand_str()]),... + MOxUnitTestNode(['c' rand_str()]),... + MOxUnitTestSuite(rand_str())}; + + for k=1:numel(replacements) + prev_nd=getTestNode(suite,1); + replacement_nd=replacements{k}; + suite=setTestNode(suite,1,replacement_nd); + assertFalse(isequal(prev_nd,getTestNode(suite,1))); + assertEqual(getTestNode(suite,1),replacement_nd); + end + +function test_test_suite_set_test_exceptions() + suite=MOxUnitTestSuite(); + nd=MOxUnitTestNode(rand_str()); + suite=addTest(suite,nd); + + bad_idxs={[1 1],.5,-1,2}; + for k=1:numel(bad_idxs) + assertExceptionThrown(@()setTestNode(suite,bad_idxs{k},nd),''); + end + + +function test_test_suite_run_partition_exceptions + aet_run=@(varargin)assertExceptionThrown(@()... + run(MOxUnitTestSuite(),... + varargin{:}),''); + aet_run(MOxUnitTestReport(),2); % missing argument + aet_run(MOxUnitTestReport(),3,2); % 3rd greater than 2nd argument + aet_run(MOxUnitTestReport(),.5,2); % non-integer + aet_run(MOxUnitTestReport(),struct(),2); % non-integer + aet_run(MOxUnitTestReport(),-1,2); % negative + aet_run(MOxUnitTestReport(),0,2); % zero + aet_run(MOxUnitTestReport(),NaN,2); % zero + aet_run(MOxUnitTestReport(),1,2.5); % non-integer + aet_run(MOxUnitTestReport(),1,struct); % non-integer + aet_run(MOxUnitTestReport(),1,-1); % negative + aet_run(MOxUnitTestReport(),1,0); % zero + aet_run(MOxUnitTestReport(),1,NaN); % zero + + + + +function test_test_suite_run() + suite=MOxUnitTestSuite(); + + test_partition_count=3+ceil(rand()*5); + ntests_per_partitions=3+ceil(rand()*5); + + ntests=ntests_per_partitions*test_partition_count; + test_cell=cell(ntests,1); + should_pass=false(ntests,1); + for k=1:ntests + p=rand()>.5; + should_pass(k)=p; + + if p + func=@do_nothing; + else + func=@()error(rand_str()); + end + + test_name=sprintf('%d',k); + test_case=MOxUnitFunctionHandleTestCase(test_name,... + rand_str(),func); + test_cell{k}=test_case; + suite=addTest(suite,test_case); + end + + verbosity=false; + has_been_tested=false(ntests,1); + for test_partition_index=1:test_partition_count + empty_report=MOxUnitTestReport(verbosity); + + report=run(suite,empty_report,test_partition_index,... + test_partition_count); + test_outcome_count=countTestOutcomes(report); + assertEqual(test_outcome_count,ntests_per_partitions); + for j=1:countTestOutcomes(report) + outcome=getTestOutcome(report,j); + test_case=getTest(outcome); + test_name=getName(test_case); + test_number=sscanf(test_name,'%d'); + + % no duplicates + assert(~has_been_tested(test_number)); + has_been_tested(test_number)=true; + + assertEqual(should_pass(test_number),isSuccess(outcome)); + end + + end + + assert(all(has_been_tested)); + + + + + +function do_nothing() + % do nothing + +function s=rand_str() + s=char(20*rand(1,10)+65); diff --git a/extern/MOxUnit/tests/test_testcase_class.m b/extern/MOxUnit/tests/test_testcase_class.m new file mode 100644 index 00000000000..6e68eaa606a --- /dev/null +++ b/extern/MOxUnit/tests/test_testcase_class.m @@ -0,0 +1,25 @@ +function test_suite=test_testcase_class() + try % assignment of 'localfunctions' is necessary in Matlab >= 2016 + test_functions=localfunctions(); + catch % no problem; early Matlab versions can use initTestSuite fine + end + initTestSuite; + +function test_assert_forwarding(t) + t.assertElementsAlmostEqual(1, 1+eps); + t.assertEqual(1, 1); + t.assertError(... + @()assertEqual([1],'a'), 'assertEqual:classNotEqual'); + t.assertExceptionThrown(... + @()assertEqual([1],'a'), 'assertEqual:classNotEqual'); + t.assertFalse(true == false); + t.assertLessThan(0, 1); + t.assertGreaterThan(1, 0); + t.assertTrue(false == false); + t.assertVectorsAlmostEqual(ones(2,1), ones(2,1)+eps); + t.assertWarning(@()warning('moxunit:warning','msg'),... + 'moxunit:warning'); + +function test_undefined_forward(t) + assertExceptionThrown(@()t.assertDoesNotExist(1, 2),... + 'moxunit:undefinedMethod'); \ No newline at end of file diff --git a/extern/MOxUnit/tools/fix_mfile_test_init.py b/extern/MOxUnit/tools/fix_mfile_test_init.py new file mode 100644 index 00000000000..1fc7022f40c --- /dev/null +++ b/extern/MOxUnit/tools/fix_mfile_test_init.py @@ -0,0 +1,226 @@ +#!/usr/bin/python +# +# Fixes issue with recent (Matlab 2016 and later) test_suites defined using +# MOxUnit, which stopped working because function handles in scripts could no +# longer refer to sub-functions in a function calling that script. +# +# This function takes a filename of a Matlab .m file, and if necessary, +# can rewrite it. By default it will only show which changes would be applied; +# to apply the changes use the --apply option +# +# It can be used together with 'find' in bash, for example: +# +# find ../tests/ -iname '*.m' | xargs -L1 ./fix_mfile_test_init.py --apply +# +# would look for tests defined in '../tests' and apply necessary changes + +from matlab_tokenizer import Tokenizer, LiteralTokenPattern, \ + OptionalTypeTokenPattern, SkipUntilTypeTokenPattern, SkipUntilTokenPattern, \ + Token, TokenPattern + +import argparse +import difflib +import logging +import sys + +DIFF_SAME = ' ' +DIFF_ADD = '+ ' +DIFF_DELETE = '- ' +DIFF_INFO = '? ' + + + +def get_fix_test_diff(tks): + ''' + Compute diff necessary to + + Input + ----- + tks: list + list of matlab_lexer.Token objects + + Returns + ------- + g: generator + generator of diff objects + ''' + Lit = LiteralTokenPattern + OptType = OptionalTypeTokenPattern + UntilType = SkipUntilTypeTokenPattern + Until = SkipUntilTokenPattern + T = Token + + # look for file with contents in the form of "function test_suite=foo" + prefix = [UntilType(T.NAME), + Lit('function', T.NAME), + UntilType(T.NAME), + Lit('test_suite', T.NAME), + UntilType(T.NAME), + Lit(None, T.NAME), + UntilType(T.NL)] + + # localfunction_lines pattern (see below) that is going to be added if not + # already in the file + infix = [OptType(T.WHITESPACE), + Lit('try', T.NAME), + UntilType(T.NAME), + Lit('test_functions', T.NAME), + OptType(T.WHITESPACE), + Lit('=', T.OP), + Until('catch', T.NAME), + Until('end', T.NAME), + UntilType(T.NL), + Lit(None, T.NL)] + + # part before which the localfunction_lines is inserted + suffix = [Lit(None, T.NL), + OptType(T.WHITESPACE), + Lit('initTestSuite', T.NAME), + UntilType(T.NL)] + + localfunction_lines = \ + [ + ' try % assignment of \'localfunctions\' is necessary in ' + 'Matlab >= 2016', + ' test_functions=localfunctions();', + ' catch % no problem; early Matlab versions can use ' + 'initTestSuite fine', + ' end'] + + pre_pos = TokenPattern.find(prefix, tks) + if pre_pos is None: + # does not match pattern, skip + return None + + suf_pos = TokenPattern.find(suffix, tks, pre_pos[1]) + if suf_pos is None: + # does not match pattern, skip + return None + + # look for infix + infix_pos = TokenPattern.find(infix, tks, pre_pos[1], suf_pos[0]) + has_infix = infix_pos is not None + + if has_infix: + # file already contains usage of localfunctions + infix_tks = tks[infix_pos[0]:infix_pos[1]] + needs_replacement = Token.join(infix_tks) != localfunction_lines + + if needs_replacement: + # no exact match, needs rewrite + prefix_tks = tks[:infix_pos[0]] + suffix_tks = tks[infix_pos[1]:] + + delta = list(difflib.ndiff(Token.get_lines(infix_tks, ''), + localfunction_lines)) + + def fix_hint(line): + if line.startswith('?') and line.endswith('\n'): + line = line[:-2] + return line + + delta = map(fix_hint, delta) + + diff_lines = [Token.get_lines(prefix_tks, DIFF_SAME), + delta, + Token.get_lines(suffix_tks, DIFF_SAME)] + else: + # no rewrite necessary + diff_lines = Token.get_lines(tks, DIFF_SAME) + else: + # needs insert of use of localfunctions + prefix_tks = tks[:suf_pos[0]] + suffix_tks = tks[1 + suf_pos[0]:] + + localfunction_lines_with_prefix = [DIFF_ADD + line + for line in localfunction_lines] + + diff_lines = [Token.get_lines(prefix_tks, DIFF_SAME), + localfunction_lines_with_prefix, + Token.get_lines(suffix_tks, DIFF_SAME)] + + + # return a generator + return (line for line in sum(diff_lines, [])) + + + +def content_changes(lines): + return len(find_content_changes(lines)) > 0 + + + +def find_content_changes(lines): + def is_diff_line(line): + prefixes = [DIFF_DELETE, DIFF_ADD, DIFF_INFO] + return any(line.startswith(prefix) for prefix in prefixes) + + return [i for i, line in enumerate(lines) if is_diff_line(line)] + + + +def get_diff_summary(lines, context=5): + line_idxs = find_content_changes(lines) + line_candidates = set(i + c for i in line_idxs + for c in xrange(-context, context + 1)) + line_keep = list(i for i in line_candidates if i >= 0 and i < len(lines)) + line_keep.sort() + + return '\n'.join([lines[i] for i in line_keep]) + + + +def process_with_args(args): + logging.basicConfig(format='%(message)s', level=args.loglevel) + + filename = args.filename + if not filename.endswith('.m'): + logging.error('File "%s" is not .m file, skip' % filename) + sys.exit(1) + + tks = Tokenizer.from_file(filename) + diff_generator = get_fix_test_diff(tks) + if diff_generator is None: + logging.error('File "%s" does not seem to define a test suite' + 'for MOxUnit, skip' % filename) + sys.exit(1) + + diff = list(diff_generator) + + if content_changes(diff): + logging.info( + 'Changes for "%s"\n%s' % (filename, get_diff_summary(diff))) + if args.apply: + fixed_lines = list(difflib.restore(diff, 2)) + + with open(filename, 'w') as f: + f.write('\n'.join(fixed_lines)) + + logging.info('File "%s" was rewritten' % filename) + + else: + logging.info('Changes not applied; use --apply to rewrite "%s"' % + filename) + + else: + logging.info('File "%s" does not require changes' % filename) + + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Fix MOxUnit test functions ' + 'to be compatible with recent ' + ' (>=2016b) versions of ' + 'Matlab') + parser.add_argument('-q', '--quiet', + help="Do not show output", + action="store_const", dest="loglevel", + const=logging.ERROR, + default=logging.INFO) + parser.add_argument('--apply', + action='store_true', + help='apply changes') + parser.add_argument('filename') + + args = parser.parse_args() + process_with_args(args) diff --git a/extern/MOxUnit/tools/matlab_tokenizer.py b/extern/MOxUnit/tools/matlab_tokenizer.py new file mode 100644 index 00000000000..4df2acaa29a --- /dev/null +++ b/extern/MOxUnit/tools/matlab_tokenizer.py @@ -0,0 +1,212 @@ +# +# simple Tokenizer for Matlab / Octave code +# + +import re + +class Token(object): + NAME = 1 + OP = 2 + COMMENT = 3 + NL = 4 + WHITESPACE = 5 + + def __init__(self, content, type): + self.content = content + self.type = type + + def __str__(self): + return '"%s" [%s]' % (self.content, self.type) + + def __repr__(self): + return self.__str__() + + @staticmethod + def join(tks, sep=''): + return sep.join(tk.content for tk in tks) + + @staticmethod + def get_lines(tks, line_prefix=''): + lines = [] + cur_line_parts = [] + + def insert_prefix(line): + return line_prefix + line + + for tk in tks: + if tk.type == Token.NL: + cur_line = insert_prefix(''.join(cur_line_parts)) + lines.append(cur_line) + cur_line_parts = [] + else: + cur_line_parts.append(tk.content) + + if len(cur_line_parts): + cur_line = insert_prefix(''.join(cur_line_parts)) + lines.append(cur_line) + + return lines + + + +class Tokenizer(object): + ''' + Simple tokenizer of Matlab code + + Splits a string up in tokens representing a name, operator, comment, + newline or comment. + + Current limitations: + - no support for line continuations + - floating point numbers may be represented by two name and one operator + - multi-line comments are not recognized + + ''' + OP_CHARS = ['(', ')', '{', '}', '@', '^', '&', '*', '-', '+' + , '=', ';', ':', '|', '<', '>', ',', ',/', '[', '\]', '.'] + + @staticmethod + def tokenize(string): + lines = string.split('\n') + + tokens = [] + + ws_sp = re.compile(r'(\s+)') + + op_re = ('([%s])' % ''.join(Tokenizer.OP_CHARS)) + op_sp = re.compile(op_re) + + for line in lines: + inside_str = False + comment_start = None + for i, c in enumerate(line): + if c == "'": + inside_str = not inside_str + elif not inside_str and c == '%': + comment_start = i + + if comment_start is None: + code_line = line + else: + code_line = line[:comment_start] + + for i, s in enumerate(ws_sp.split(code_line)): + is_whitespace = i % 2 == 1 + if is_whitespace: + tk = Token(s, Token.WHITESPACE) + tokens.append(tk) + else: + for j, t in enumerate(op_sp.split(s)): + is_op = j % 2 == 1 + if len(t) == 0: + continue + type = Token.OP if is_op else Token.NAME + tk = Token(t, type) + tokens.append(tk) + + if comment_start is not None: + comment = line[comment_start:] + tk = Token(comment, Token.COMMENT) + tokens.append(tk) + + tokens.append(Token('\n', Token.NL)) + + return tokens + + @staticmethod + def from_file(fn): + with open(fn) as f: + string = f.read() + return Tokenizer.tokenize(string) + + + +class TokenPattern(object): + def __init__(self, content, type): + self.content = content + self.type = type + + def _matches(self, tk): + raise NotImplementedError + + def __str__(self): + return '%s(%s,%s)' % (self.__class__.__name__, + self.content, + self.type) + + @staticmethod + def find(token_pats, tks, start=0, stop=None): + n_pat = len(token_pats) + n_tk = len(tks) + + for i_start in xrange(start, n_tk - n_pat): + if stop is not None and i_start >= stop: + break + + matches = True + tk_pos = i_start + + for j, token_pat in enumerate(token_pats): + tk_pos = token_pat._matches(tks, tk_pos) + if tk_pos is None: + matches = False + break + + if matches: + return (i_start, tk_pos) + + return None + + + +class LiteralTokenPattern(TokenPattern): + def _matches(self, tks, pos): + tk = tks[pos] + if (self.content is not None and + self.content != tk.content): + return None + + if (self.type is not None and + self.type != tk.type): + return None + + return pos + 1 + + + +class OptionalTypeTokenPattern(TokenPattern): + def __init__(self, type): + super(OptionalTypeTokenPattern, self).__init__(None, type) + + def _matches(self, tks, pos): + for i in xrange(pos, len(tks)): + tk = tks[i] + same_type = self.type == tk.type + + if not same_type: + break + + return i + + + +class SkipUntilTokenPattern(TokenPattern): + def _matches(self, tks, pos): + n_tks = len(tks) + for i in xrange(pos, n_tks): + tk = tks[i] + + matches_content = self.content is None or tk.content == self.content + matches_type = self.type == tk.type + + if matches_content and matches_type: + return i + + return None + + + +class SkipUntilTypeTokenPattern(SkipUntilTokenPattern): + def __init__(self, type): + super(SkipUntilTypeTokenPattern, self).__init__(None, type) +