test randperm

function test_suite = test_randperm
    % tests for cosmo_randperm
    %
    % #   For CoSMoMVPA's copyright information and license terms,   #
    % #   see the COPYING file distributed with CoSMoMVPA.           #
    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_randperm_no_seed
    helper_test_deterministic(false);

function test_randperm_with_seed
    helper_test_deterministic(true);

function helper_test_deterministic(with_seed)
    if with_seed
        seed = ceil(rand() * 1e5);
        args = {'seed', seed};
    else
        args = {};
    end

    f = @(varargin)cosmo_randperm(varargin{:}, args{:});

    % single input
    n = randint();
    x1 = f(n);
    assertEqual(size(x1), [1 n]);
    assertEqual(sort(x1), 1:n);

    x1a = f(n);
    if with_seed
        assertEqual(x1, x1a);
    else
        assert(~isequal(x1, x1a));
    end

    % two inputs, select all
    x1a = f(n, n);

    if with_seed
        assertEqual(x1, x1a);
    else
        assert(~isequal(x1, x1a));
    end

    % two inputs, k<n
    k = randint();
    n = k + randint();
    assert(k < n);
    x2 = f(n, k);
    assertEqual(size(x2), [1 k]);
    msk = bsxfun(@eq, (1:n)', x2);
    assertEqual(sum(msk(:) == 1), k);
    assertEqual(sum(sum(msk == 1, 1), 2), k); % each column has one 1
    assertEqual(sum(sum(msk == 1, 2), 1), k); % each row has one 1

    if with_seed
        % deterministic
        x2a = f(n, k);
        assertEqual(x2, x2a);
    else
        found = false;
        for attempt = 1:10
            x2a = f(n, k);
            if ~isequal(x2, x2a)
                found = true;
                break
            end
        end

        if ~found
            error('Different calls do not lead to different outputs');
        end
    end

    x3 = f(0);
    assertTrue(isempty(x3));

    x4 = f(1);
    assertEqual(x4, 1);

    x5 = f(1, 1);
    assertEqual(x5, 1);

function test_randperm_different_seeds
    count = 10;
    seed = 0;
    result = cell(count, 1);
    for k = 1:count
        seed = seed + randint();

        result{k} = cosmo_randperm(1000, 'seed', seed);
        for j = 1:(k - 1)
            assertFalse(isequal(result{j}, result{k}));
        end
    end

function test_randperm_exceptions
    aet = @(varargin)assertExceptionThrown(@() ...
                                           cosmo_randperm(varargin{:}), '');
    illegal_args = {-1, .5, [1 2], 'foo', struct, cell(0)};
    narg = numel(illegal_args);

    % illegal first and/or second arguments
    for k = 1:narg
        for second_arg = [false, true]
            for j = 1:narg
                if second_arg
                    args = illegal_args([k j]);
                elseif j > 1
                    continue
                else
                    % single arg if j==1
                    args = illegal_args(k);
                end
                aet(args{:});
            end
        end
    end

    % illegal seed arguments
    for k = 1:narg
        arg = illegal_args(k);
        aet(4, 2, 'seed', arg{:});
    end

    % missing seed
    aet(4, 2, 'seed');

    % double seed
    aet(4, 2, 'seed', 1, 'seed', 1);

    % 3 numeric arguments
    aet(4, 2, 1);

    % second argument greater than first
    aet(2, 4);
    aet(2, 4, 'seed', 1);

    % unknown keyword
    aet(2, 'foo', 1);
    aet(2, 'foo');

function x = randint(n)
    x = ceil(10 + rand() * 50);