diff --git a/tests/default.toml b/tests/default.toml new file mode 100644 index 0000000..55ae0ce --- /dev/null +++ b/tests/default.toml @@ -0,0 +1,5 @@ +[qbittorrent] +host = "127.0.0.1" # qbittorrent webui-api hostname/ip +port = $FREEPORT # qbittorrent webui-api port +login = "admin" # qbittorrent webui-api user +password = "adminadmin" # qbittorrent webui-api password diff --git a/tests/qbittorrent-nox.sh b/tests/qbittorrent-nox.sh new file mode 100755 index 0000000..ab347f4 --- /dev/null +++ b/tests/qbittorrent-nox.sh @@ -0,0 +1,85 @@ +#! /usr/bin/env bash + +# qbittorrent-nox.sh start|stop DIR PORT +# DIR will contain the qbittorrent-nox profile, as well as "pid" and "qBottorrent.log" +# PORT configures port for qbittorrent-nox web API + +# Wait for qBittorrent WEB API to come online at address $1 +# waitforqBittorrentStart "http://localhost:1312" +function waitforqBittorrentStart() { + #set +x + TIMESTAMP=$(date +%s) + END=$((TIMESTAMP+10)) + ERR=0 + while true; do + NEWTIMESTAMP=$(date +%s) + if [ $NEWTIMESTAMP -gt $END ]; then + ERR=1 + break + fi + if curl --silent "$1" 2>&1 > /dev/null; then + break + else + sleep 0.1 + fi + done + return $ERR +} + +# Wait for qBittorrent to be done cleanly exiting +# Necessary because otherwise it will leave temporary files behind! +# waitforQbittorrentStop 1234 +function waitforqBittorrentStop() { + TIMESTAMP=$(date +%s) + END=$((TIMESTAMP+15)) + ERR=0 + while true; do + NEWTIMESTAMP=$(date +%s) + if [ $NEWTIMESTAMP -gt $END ]; then + ERR=1 + break + fi + if ! ps x | grep -P "^\s+$PID\s+" 2>&1 > /dev/null; then + # process died successfully + break + else + sleep 0.1 + fi + done + return $ERR +} + +start() { + echo "y" | qbittorrent-nox --profile="$1" --webui-port="$2" 2>&1 > "$1"/qBittorrent.log & + echo $! > "$1"/pid + if waitforqBittorrentStart "http://localhost:$2"; then + return 0 + else + return 1 + fi +} + +stop() { + PID="$(cat "$1"/pid)" + kill $PID + if ! waitforqBittorrentStop $PID; then + echo "qBittorrent does not quit. Using SIGKILL" + kill -9 $PID + fi +} + +case "$1" in + "start") + QBTNOX_DIR="$2" + QBTNOX_PORT="$3" + start "$QBTNOX_DIR" "$QBTNOX_PORT" + STATUS=$? + ;; + "stop") + QBTNOX_DIR="$2" + stop "$QBTNOX_DIR" + STATUS=$? + ;; +esac + +exit $STATUS diff --git a/tests/runners/local.sh b/tests/runners/local.sh new file mode 100755 index 0000000..ec7787e --- /dev/null +++ b/tests/runners/local.sh @@ -0,0 +1,44 @@ +#! /usr/bin/bash + +DEPENDENCIES=("qbittorrent-nox" "bats" "curl" "grep" "cat" "sed") + +if [[ "${QBTNOX_DIR:-plz}" = "plz" ]]; then + export QBTNOX_DIR="$(mktemp -d)" +fi + +ORIGDIR="$(pwd)" +cd "$(dirname "$0")" + +source ../utils.sh + +SUBCOMMAND="${1:-test}" +if [[ "$SUBCOMMAND" = "test" ]] && [[ "${TESTRUNNER:-plz}" = "plz" ]]; then + depsCheck +fi + +export FREEPORT="$(findFreePort)" +API="http://localhost:$FREEPORT" + +# Subcommand is test and checks succeeded +subcommand "$SUBCOMMAND" +STATUS=$? +if [ $STATUS -eq 0 ]; then + if [[ "$SUBCOMMAND" = "test" ]]; then + cat ../default.toml | envsubst > "$QBTNOX_DIR"/config.toml + export CFGFILE="$QBTNOX_DIR"/config.toml + if ../qbittorrent-nox.sh start "$QBTNOX_DIR" "$FREEPORT"; then + runTests "$API" + STATUS=$? + ../qbittorrent-nox.sh stop "$QBTNOX_DIR" + else + echo "qBittorrent did not start" + STATUS=1 + fi + fi +fi + +if [[ "${TESTRUNNER:-plz}" = "plz" ]]; then + rm -R "$QBTNOX_DIR" +fi +cd "$ORIGDIR" +exit $STATUS diff --git a/tests/runners/nix.sh b/tests/runners/nix.sh new file mode 100755 index 0000000..7f1765a --- /dev/null +++ b/tests/runners/nix.sh @@ -0,0 +1,56 @@ +#! /usr/bin/env bash + +DEPENDENCIES=("nix" "nix-shell") +# TODO: find grep package name for --pure environment +NIX_DEPENDENCIES=("bats" "curl" "qbittorrent-nox") + +if [[ "${QBTNOX_DIR:-plz}" = "plz" ]]; then + export QBTNOX_DIR="$(mktemp -d)" +fi + +ORIGDIR="$(pwd)" +cd "$(dirname "$0")" + +source ../utils.sh + +SUBCOMMAND="${1:-test}" + +# Ensure nix shell with bats and curl... if we are running tests +if [[ "$SUBCOMMAND" = "test" ]] && [[ "${IN_NIX_SHELL:-plz}" = "plz" ]]; then + # Did test.sh perform depsCheck already? + if [[ "${TESTRUNNER:-plz}" = "plz" ]]; then + depsCheck + fi + # Are we in nix-shell already? + # TODO: redo --pure when grep dep is found + IN_NIX_SHELL=1 nix-shell -p ${NIX_DEPENDENCIES[@]} --run "\"$ORIGDIR\"/\"$0\" "$@"" + exit $? +fi + + +export FREEPORT="$(findFreePort)" +API="http://localhost:$FREEPORT" + +# Subcommand is test and checks succeeded +subcommand "$SUBCOMMAND" +STATUS=$? +if [ $STATUS -eq 0 ]; then + if [[ "$SUBCOMMAND" = "test" ]]; then + cat ../default.toml | envsubst > "$QBTNOX_DIR"/config.toml + export CFGFILE="$QBTNOX_DIR"/config.toml + if ../qbittorrent-nox.sh start "$QBTNOX_DIR" "$FREEPORT"; then + runTests "$API" + STATUS=$? + ../qbittorrent-nox.sh stop "$QBTNOX_DIR" + else + echo "qBittorrent did not start" + STATUS=1 + fi + fi +fi + +if [[ "${TESTRUNNER:-plz}" = "plz" ]]; then + rm -R "$QBTNOX_DIR" +fi +cd "$ORIGDIR" +exit $STATUS diff --git a/tests/test.sh b/tests/test.sh new file mode 100755 index 0000000..bd962a3 --- /dev/null +++ b/tests/test.sh @@ -0,0 +1,94 @@ +#! /usr/bin/env bash + +# LET TEST RUNNERS KNOW WE ALREADY PERFORM CHECKS +export TESTRUNNER="YEAH" + +export QBTNOX_DIR="$(mktemp -d)" + +ORIGDIR="$(pwd)" +cd "$(dirname "$0")" + +help() { + echo "test.sh RUNNER" + echo " Run the test suite. Requires bats and qBittorrent as dependencies. Dependencies are found by one of the following runners:" + echo " - local: the local system's \$PATH" + echo " - nix: nixos.org package repositories (requires working nix setup)" + echo " - docker: Debian stable base image + APT packages (requires working docker setup/daemon)" + echo " (default) if no explicit runner is requested, they are tried in order." +} + +# FIND THE QBT BINARY +cd .. +BIN_NAME="$(grep -Po '^name = (.*)' Cargo.toml | sed 's/[" ]//g' | sed 's/name=//')" +if [ -f target/release/$BIN_NAME ]; then + cargo build --release + export BIN_PATH="$(realpath target/release/"$BIN_NAME")" +else + cargo build + export BIN_PATH="$(realpath target/debug/"$BIN_NAME")" +fi +echo "Using build $BIN_PATH" + +cd tests +SUCCESS=0 +RUNNER="${1:-default}" +shift + +case "$RUNNER" in + "--help"|"-h"|"help") + help + ;; +# "docker") +# echo "Running tests under: docker ("$QBTNOX_DIR")" +# if ./runners/docker.sh deps-check; then +# ./runners/docker.sh test +# SUCCESS=$? +# else +# SUCCESS=1 +# fi +# ;; + "nix") + echo "Running tests under: nix ("$QBTNOX_DIR")" + if ./runners/nix.sh deps-check; then + ./runners/nix.sh test + SUCCESS=$? + else + SUCCESS=1 + fi + ;; + "local") + echo "Running tests under: local ("$QBTNOX_DIR")" + if ./runners/.local.sh deps-check; then + ./runners/local.sh test + SUCCESS=$? + else + SUCCESS=1 + fi + ;; + *) + echo "Autodiscovery for runner" + if ./runners/local.sh deps-check; then + echo "Running tests under: local ("$QBTNOX_DIR")" + ./runners/local.sh test + SUCCESS=$? + elif ./runners/nix.sh deps-check; then + echo "Running tests under: nix ("$QBTNOX_DIR")" + ./runners/nix.sh test + SUCCESS=$? +# elif ./runners/docker.sh deps-check; then +# echo "Running tests under: docker" +# ./runners/docker.sh test +# SUCCESS=$? + else + echo "Failed to find a test runner! Please setup qbittorrent-nox and bats on your system, or alternatively setup the nix package manager or the docker container manager to install them dynamically." + SUCCESS=1 + fi + ;; +esac + +if [ -d "$QBTNOX_DIR" ]; then + rm -R "$QBTNOX_DIR" +fi + +cd "$ORIGDIR" +exit $SUCCESS diff --git a/tests/units/main.bats b/tests/units/main.bats new file mode 100644 index 0000000..f2f211c --- /dev/null +++ b/tests/units/main.bats @@ -0,0 +1,51 @@ +#! /usr/bin/env bats + +setup() { + bats_require_minimum_version 1.5.0 +} + +@test "wrong server" { + cat "$CFGFILE" | sed "s/$FREEPORT/22/" > "$CFGFILE".wrongserver.toml + cat "$CFGFILE".wrongserver.toml + run -1 "$BIN_PATH" -c "$CFGFILE".wrongserver.toml list + echo "$output" + echo "$output" | grep "request error: error sending request" +} + +@test "wrong login" { + cat "$CFGFILE" | sed 's/admin/user/g' > "$CFGFILE".wronguser.toml + cat "$CFGFILE".wronguser.toml + run -1 "$BIN_PATH" -c "$CFGFILE".wronguser.toml list + echo "$output" + echo "$output" | grep "Failed to login.*user" +} + +@test "empty list" { + run "$BIN_PATH" -c "$CFGFILE" list + echo "$output" + [[ "$output" = "[]" ]] +} + +@test "add ISO" { + run "$BIN_PATH" -c "$CFGFILE" add --paused "magnet:?xt=urn:btih:a982743fdb1115a0e501cabb75cca85c828f9445&dn=tails-amd64-4.29-img&tr=udp%3a%2f%2ftracker.torrent.eu.org%3a451&tr=udp%3a%2f%2ftracker.coppersurfer.tk%3a6969" + echo "$output" + [[ "$output" = "" ]] +} + +@test "add broken magnet" { + run -1 "$BIN_PATH" -c "$CFGFILE" add --paused "magnet:?qsdjksqdjsqdsqdsqldkqs" + echo "$output" + echo "$output" | grep "Other error:" +} + +@test "get info about ISO" { + run "$BIN_PATH" -c "$CFGFILE" get "a982743fdb1115a0e501cabb75cca85c828f9445" + echo "$output" + echo "$output" | grep 'a982743fdb1115a0e501cabb75cca85c828f9445' +} + +@test "non-empty list" { + run "$BIN_PATH" -c "$CFGFILE" list + echo "$output" + echo "$output" | grep "a982743fdb1115a0e501cabb75cca85c828f9445" +} diff --git a/tests/utils.sh b/tests/utils.sh new file mode 100755 index 0000000..9d1eaaa --- /dev/null +++ b/tests/utils.sh @@ -0,0 +1,94 @@ +#! /usr/bin/bash + +#echo "-- " "$0" "$@" "(utils)" + +function help() { + echo "$(basename "$0") SUBCOMMAND" + echo " test: run the test suite using this runner" + echo " deps-check: check for this runner's dependencies" +} + +function depsCheck() { + # Explicit arguments, or $DEPENDENCIES + echo "-- Dependency checks for "$(basename "$0")"" + if [ ! $# -eq 0 ]; then + DEPS=("$@") + else + DEPS=("${DEPENDENCIES[@]}") + fi + MISSING=() + for i in "${DEPS[@]}"; do + if ! command -v "$i" >/dev/null 2>&1; then + MISSING+=("$i") + fi + done + if [ ${#MISSING[@]} -gt 0 ]; then + echo "ERROR: missing dependencies: "${MISSING[@]}"" + return 1 + fi +} + +function subcommand() { + SUBCOMMAND="${1:-test}" + #echo " Subcommand "$1" evaluated to "$SUBCOMMAND"" + case "$SUBCOMMAND" in + "help"|"--help"|"-h") + help + return 1 + ;; + "deps-check") + depsCheck + return $? + ;; + "test") + echo "Running tests from "$QBTNOX_DIR"" + return $? + ;; + *) + echo "$(basename "$0") ERROR: wrong subcommand "$SUBCOMMAND"" + return 2 + ;; + esac +} + +# $1 exit code +# $2 reason +# $3 orig dir to return to +function abortIfError() { + if [ ! $1 -eq 0 ]; then + cd "$ORIGDIR" + echo "$2" + #set +x + if [ $# -eq 3 ]; then + cd "$3" + fi + exit $1 + fi +} + +# finds a free TCP port +# https://stackoverflow.com/questions/28989069/how-to-find-a-free-tcp-port/45539101#45539101 +function findFreePort() { + BASE_PORT=16998 + INCREMENT=1 + + port=$BASE_PORT + isfree=$(netstat -taln | grep $port) + + while [[ -n "$isfree" ]]; do + port=$[port+INCREMENT] + isfree=$(netstat -taln | grep $port) + done + + echo "$port" +} + +# runTests APIADDR +function runTests { + echo "Running tests at "$1"" + if bats ../units/; then + echo "OK" + else + echo "FAIL" + fi +}