#!/usr/bin/env bash

# THIS FILE IS PART OF THE CYLC WORKFLOW ENGINE.
# Copyright (C) NIWA & British Crown (Met Office) & Contributors.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

usage() {
  cat <<eof
Usage: run-functional-tests [...]

Run the Cylc test battery, in <CYLC_REPO_DIR>/tests.

Options and arguments are appended to "prove -j \$NPROC -s -r \${@:-tests/f}".
NPROC is the number of concurrent processes to run, which defaults to the
global config "[scheduler]process pool size" setting.

The tests ignore normal site/user global config and instead use:
   ~/.cylc/flow/<cylc-version>/global-tests.cylc
This should specify test platforms under the [platforms] section. Additional
global config items can be added on the fly using the create_test_global_config
shell function defined in the test_header.

Workflow run directories are only cleaned up for passing tests on the workflow
host.

Set "export CYLC_TEST_DEBUG=true" to print failed-test stderr to the terminal.

To change the test file comparison command from "diff -u" do (for example):
export CYLC_TEST_DIFF_CMD='xxdiff -D'

Some test workflows submit jobs to the 'at' so atd must be up on the test
platforms.

Commits or Pull Requests to cylc/cylc-flow on GitHub will trigger
GitHub Actions to run tests, see .github/workflows for details.

By default all tests are executed.  To run just a subset of them:
  * list individual tests or test directories to run on the command line
  * list individual tests or test directories to skip in \$CYLC_TEST_SKIP
  List specific tests relative to \$CYLC_REPO_DIR (i.e. starting with "test/").
Some platform-specific tests are automatically skipped, depending on platform.


Options:
  -h, --help       Print this help message and exit.
  -p, --platform   Specify one or more platforms to use for running the
                   tests.

                   Platforms should be configured in global-test.cylc.

                   Tests will run on the first compatible platform.

                   Defaults to:
                       host: localhost
                       job runner: background

                   Can be set to a glob. e.g. '_local*' will match all
                   configuted test platforms with local job submission.

                   '*' will match all configured test platforms.

                   Alternatively provide a space separated string of platforms
                   using the CYLC_TEST_PLATFORMS environment variable.

Examples:
  # Run the full test suite with the default options.
  $ run-functional-tests

  # Run the full test suite with 12 processes
  $ run-functional-tests -j 12

  # Run only tests under "tests/f/cyclers/"
  $ run-functional-tests tests/f/cyclers

  # Run only "tests/f/cyclers/16-weekly.t" in verbose mode
  $ run-functional-tests -v tests/f/cyclers/16-weekly.t

  # Run only tests under "tests/f/cyclers/", and skip 00-daily.t
  $ export CYLC_TEST_SKIP=tests/f/cyclers/00-daily.t
  $ run-functional-tests tests/f/cyclers

  # Run the first quarter of the test battery
  $ CHUNK=1/4 run-functional-tests

  # Re-run failed tests
  $ run-functional-tests --state=save
  $ run-functional-tests --state=failed

  # run tests on a specified test platform
  $ run-functional-tests -p <test_platform> <path>

  # run tests using the first matching test platform from the test config
  # (test platforms have names beggining with an underscore)
  $ run-functional-tests -p all <path>
eof
}

# parse args
PROVE_ARGS=()
TESTS=()
# must use string as can't export arrays in bash
PLATFORMS="${CYLC_TEST_PLATFORMS:-}"
PROVIDE_TESTBASE=true
while [[ $# -gt 0 ]]; do
    case "$1" in
        --help|-h)
            usage
            exit 0
            ;;
        -p|--platform)
            # note "all" logic is handled in the test_header
            PLATFORMS="$PLATFORMS $2"
            shift
            shift
            ;;
        --state*)
            STATES=${1/--state=/}
            if [[ -n ${STATES/save/} ]]; then
                # run tests from a previous saved state, don't provide a list
                # of tests to run as prove does this for us
                PROVIDE_TESTBASE=false
            fi
            PROVE_ARGS+=("$1")
            shift
            ;;
        -j|-a)
            PROVE_ARGS+=("$1" "$2")
            shift
            shift
            ;;
        -*)
            PROVE_ARGS+=("$1")
            shift
            ;;
        *)
            TESTS+=("$1")
            shift
            ;;
    esac
done

# base dir
cd "$(dirname "$0")/../.." || exit 1
export CYLC_REPO_DIR="${PWD}"

export PLATFORMS

# default test base
if [[ ${#TESTS[@]} -eq 0 ]]; then
    TESTS=('tests/functional')
fi

# handle chunking
if [[ -n "${CHUNK:-}" ]]; then
    TEMP=("${TESTS[@]}")
    TESTS=()
    # would be a lot nicer with mapfile but we should keep bash3 support
    while IFS='' read -r line; do TESTS+=("$line"); done < <(
        prove "${TEMP[@]}" --dry --recurse | sort | split -n "r/${CHUNK}"
    )
fi

if ! $PROVIDE_TESTBASE; then
    TESTS=()
fi

# ensure that TMPDIR is not a symlink
# (we rely on path comparisons in many tests)
TMPDIR="$(realpath "${TMPDIR:-/tmp}")"
export TMPDIR

# test environment
export CYLC_TEST_SKIP=${CYLC_TEST_SKIP:-}
CYLC_TEST_TIME_INIT="$(date -u +'%Y%m%dT%H%M%SZ')"
CYLC_TEST_UID="$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c4)"
CYLC_TEST_REG_BASE="${CYLC_TEST_REG_BASE:-cylctb-${CYLC_TEST_TIME_INIT}-${CYLC_TEST_UID}}"
export CYLC_TEST_REG_BASE
export CYLC_TEST_TIME_INIT

# get number of cores
NPROC=$(python3 -c 'import multiprocessing as mp; print(mp.cpu_count())')

# run tests run
exec prove --timer -j "${NPROC}" -s -r "${PROVE_ARGS[@]}" "${TESTS[@]}"
