diff --git a/.gitignore b/.gitignore
index 2468476e..1d166b12 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,10 @@ missing
mkinstalldirs
*.tar.gz
config.h.in~
+
+# Ignore test output files
+/tests/*.log
+/tests/*.trs
+
+# Ignore auxiliary files
+/tap-driver.sh
diff --git a/Makefile.am b/Makefile.am
index 26f4cae3..d1131949 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3,7 +3,7 @@
AUTOMAKE_OPTIONS = foreign dist-zip
ACLOCAL_AMFLAGS = -I m4
-SUBDIRS = src conf doc web admin win32 examples
+SUBDIRS = src conf doc web admin win32 examples tests
EXTRA_DIST = README.md HACKING m4/acx_pthread.m4 m4/ogg.m4 \
m4/theora.m4 m4/vorbis.m4 m4/speex.m4 \
diff --git a/configure.ac b/configure.ac
index 1061da5d..86663921 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3,6 +3,7 @@ AC_INIT([Icecast], [2.4.99.2], [icecast@xiph.org])
AC_PREREQ([2.54])
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_MACRO_DIR([m4])
+AC_REQUIRE_AUX_FILE([tap-driver.sh])
dnl Process this file with autoconf to produce a configure script.
AM_INIT_AUTOMAKE
@@ -10,6 +11,7 @@ AM_CONFIG_HEADER([config.h])
AM_MAINTAINER_MODE
AC_PROG_CC
+AC_PROG_AWK
AC_CANONICAL_HOST
AC_PROG_LIBTOOL
AC_SYS_LARGEFILE
@@ -163,4 +165,4 @@ AC_OUTPUT([Makefile conf/Makefile src/Makefile src/common/avl/Makefile
src/common/httpp/Makefile src/common/thread/Makefile src/common/log/Makefile
src/common/net/Makefile src/common/timing/Makefile doc/Makefile web/Makefile
web/assets/Makefile web/assets/css/Makefile web/assets/font/Makefile
-admin/Makefile admin/includes/Makefile win32/Makefile examples/Makefile])
+admin/Makefile admin/includes/Makefile win32/Makefile examples/Makefile tests/Makefile])
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 00000000..e445847d
--- /dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,14 @@
+## Process this file with automake to produce Makefile.in
+
+TEST_LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) \
+ $(top_srcdir)/tap-driver.sh
+
+TESTS = \
+ startup.test \
+ admin.test
+
+EXTRA_DIST = $(TESTS)
+
+EXTRA_DIST += \
+ icecast.xml \
+ on-connect.sh
diff --git a/tests/admin-tests.sh b/tests/admin-tests.sh
deleted file mode 100755
index eba336bf..00000000
--- a/tests/admin-tests.sh
+++ /dev/null
@@ -1,150 +0,0 @@
-#!/bin/bash
-
-testdir=$(dirname "$0")
-test -n "$testdir" && cd "$testdir" || exit
-
-ICECAST_BASE_URL="http://localhost:8000/"
-
-RETURN=0
-MOUNT_LISTENER_AUTH="foo.ogg"
-MOUNT_SOURCE_AUTH="test.ogg"
-
-AUTH_MOUNT="foobar:hackmemore"
-AUTH_SOURCE="source:hackme"
-AUTH_ADMIN="admin:hackme"
-
-L_USER="foo"
-L_PASS="bar"
-L_AUTH="$L_USER:$L_PASS"
-
-echo "Starting Icecast"
-../src/icecast -c ./icecast.xml 2> /dev/null &
-ICECAST_PID=$!
-sleep 5
-
-echo "Starting Source client on /$MOUNT_LISTENER_AUTH"
-ffmpeg -loglevel panic -re -f lavfi -i "sine=frequency=1000" -content_type application/ogg "icecast://$AUTH_SOURCE@127.0.0.1:8000/$MOUNT_LISTENER_AUTH" &
-SOURCE1_PID=$!
-
-echo "Starting Source client on /$MOUNT_SOURCE_AUTH"
-ffmpeg -loglevel panic -re -f lavfi -i "sine=frequency=1000" -content_type application/ogg "icecast://$AUTH_MOUNT@127.0.0.1:8000/$MOUNT_SOURCE_AUTH" &
-SOURCE2_PID=$!
-
-function test_endpoint {
- echo " CURL $1"
- if test "x$3" == "x"; then
- res=$(curl -m 5 -o /dev/null -D - "$ICECAST_BASE_URL$1" 2>/dev/null | head -n 1 | cut -d$' ' -f2- | tr -d '\r\n')
- else
- res=$(curl -m 5 -u "$3" -o /dev/null -D - "$ICECAST_BASE_URL$1" 2>/dev/null | head -n 1 | cut -d$' ' -f2- | tr -d '\r\n')
- fi
- if test "$(echo "$res" | cut -d$' ' -f1)" -eq "$2"; then
- printf " \e[92mOK\e[39m"
- echo " [$res]"
- else
- printf " \e[91mFAIL\e[39m"
- echo " [$res] Expected: $2"
- RETURN=1
- fi
-}
-
-echo
-echo "Testing admin/manageauth endpoint"
-test_endpoint "admin/manageauth?id=4&username=$L_USER&password=$L_PASS&action=add" 401
-test_endpoint "admin/manageauth?id=99&username=$L_USER&password=$L_PASS&action=add" 401
-test_endpoint "admin/manageauth?id=99&username=$L_USER&password=$L_PASS&action=add" 404 "$AUTH_ADMIN"
-test_endpoint "admin/manageauth?id=4&username=$L_USER&password=$L_PASS&action=add" 200 "$AUTH_ADMIN"
-
-echo
-echo "Testing admin/buildm3u endpoint"
-test_endpoint "admin/buildm3u?username=$L_USER&password=$L_PASS&mount=%2F$MOUNT_LISTENER_AUTH" 200
-test_endpoint "admin/buildm3u?username=something&password=foo&mount=%2F$MOUNT_LISTENER_AUTH" 200
-
-echo
-echo "Testing admin/stats endpoint"
-test_endpoint "admin/stats" 401
-test_endpoint "admin/stats" 401 "$AUTH_SOURCE"
-test_endpoint "admin/stats" 401 "$L_AUTH"
-test_endpoint "admin/stats" 200 "$AUTH_ADMIN"
-
-echo
-echo "Testing admin/listclients endpoint"
-test_endpoint "admin/listclients" 401
-test_endpoint "admin/listclients" 401 "$AUTH_SOURCE"
-test_endpoint "admin/listclients" 401 "$L_AUTH"
-test_endpoint "admin/listclients" 400 "$AUTH_ADMIN"
-test_endpoint "admin/listclients?mount=%2F$MOUNT_LISTENER_AUTH" 200 "$AUTH_ADMIN"
-
-echo
-echo "Testing admin/moveclients endpoint"
-test_endpoint "admin/moveclients" 401
-test_endpoint "admin/moveclients" 401 "$AUTH_SOURCE"
-test_endpoint "admin/moveclients" 401 "$L_AUTH"
-test_endpoint "admin/moveclients" 400 "$AUTH_ADMIN"
-test_endpoint "admin/moveclients?mount=%2F$MOUNT_LISTENER_AUTH" 200 "$AUTH_ADMIN"
-
-echo
-echo "Testing admin/updatemetadata endpoint"
-test_endpoint "admin/updatemetadata" 401
-test_endpoint "admin/updatemetadata" 401 "$AUTH_SOURCE"
-test_endpoint "admin/updatemetadata" 401 "$L_AUTH"
-test_endpoint "admin/updatemetadata" 400 "$AUTH_ADMIN"
-test_endpoint "admin/updatemetadata?mount=%2F$MOUNT_LISTENER_AUTH" 200 "$AUTH_ADMIN"
-
-echo
-echo "Testing admin/metadata endpoint"
-test_endpoint "admin/metadata" 401
-test_endpoint "admin/metadata" 401 "$AUTH_SOURCE"
-test_endpoint "admin/metadata" 401 "$L_AUTH"
-test_endpoint "admin/metadata" 400 "$AUTH_ADMIN"
-test_endpoint "admin/metadata?mount=%2F$MOUNT_LISTENER_AUTH&mode=updinfo&charset=UTF-8&song=Test1" 200 "$AUTH_SOURCE"
-test_endpoint "admin/metadata?mount=%2F$MOUNT_LISTENER_AUTH&mode=updinfo&charset=UTF-8&song=Test2" 200 "$AUTH_ADMIN"
-test_endpoint "admin/metadata?mount=%2F$MOUNT_SOURCE_AUTH&mode=updinfo&charset=UTF-8&song=Test1" 401 "$AUTH_SOURCE"
-test_endpoint "admin/metadata?mount=%2F$MOUNT_SOURCE_AUTH&mode=updinfo&charset=UTF-8&song=Test2" 200 "$AUTH_ADMIN"
-test_endpoint "admin/metadata?mount=%2F$MOUNT_SOURCE_AUTH&mode=updinfo&charset=UTF-8&song=Test3" 200 "$AUTH_MOUNT"
-
-echo
-echo "Testing admin/listmounts endpoint"
-test_endpoint "admin/listmounts" 401
-test_endpoint "admin/listmounts" 401 "$AUTH_SOURCE"
-test_endpoint "admin/listmounts" 401 "$L_AUTH"
-test_endpoint "admin/listmounts" 200 "$AUTH_ADMIN"
-test_endpoint "admin/listmounts?mount=%2F$MOUNT_LISTENER_AUTH" 400 "$AUTH_ADMIN"
-
-echo
-echo "Testing mountpoint which requires auth"
-test_endpoint "$MOUNT_LISTENER_AUTH" 401
-test_endpoint "$MOUNT_LISTENER_AUTH" 401 "$AUTH_SOURCE"
-test_endpoint "$MOUNT_LISTENER_AUTH" 200 "$L_AUTH"
-test_endpoint "$MOUNT_LISTENER_AUTH" 401 "$AUTH_ADMIN"
-
-echo
-echo "Testing mountpoint which doesn't require auth"
-test_endpoint "$MOUNT_SOURCE_AUTH" 200
-test_endpoint "$MOUNT_SOURCE_AUTH" 200 "$AUTH_SOURCE"
-test_endpoint "$MOUNT_SOURCE_AUTH" 200 "$L_AUTH"
-test_endpoint "$MOUNT_SOURCE_AUTH" 200 "$AUTH_ADMIN"
-
-echo
-echo "Testing admin/killsource endpoint"
-test_endpoint "admin/killsource" 401
-test_endpoint "admin/killsource" 401 "$AUTH_SOURCE"
-test_endpoint "admin/killsource" 401 "$L_AUTH"
-test_endpoint "admin/killsource" 400 "$AUTH_ADMIN"
-test_endpoint "admin/killsource?mount=%2F$MOUNT_LISTENER_AUTH" 200 "$AUTH_ADMIN"
-
-echo
-echo "All tests done"
-echo
-
-if kill $SOURCE1_PID; then
- echo "Terminated SOURCE1"
-fi
-if kill $SOURCE2_PID; then
- echo "Terminated SOURCE2"
-fi
-
-if kill $ICECAST_PID; then
- echo "Terminated Icecast"
-fi
-
-exit $RETURN
diff --git a/tests/admin.test b/tests/admin.test
new file mode 100755
index 00000000..e41cdbad
--- /dev/null
+++ b/tests/admin.test
@@ -0,0 +1,184 @@
+#!/bin/bash
+
+testdir=$(dirname "$0")
+test -n "$testdir" && cd "$testdir" || exit
+
+ICECAST_BASE_URL="http://localhost:8000/"
+counter=1
+MOUNT_LISTENER_AUTH="foo.ogg"
+MOUNT_SOURCE_AUTH="test.ogg"
+
+AUTH_MOUNT="foobar:hackmemore"
+AUTH_SOURCE="source:hackme"
+AUTH_ADMIN="admin:hackme"
+
+L_USER="foo"
+L_PASS="bar"
+L_AUTH="$L_USER:$L_PASS"
+
+echo "# Starting Icecast"
+../src/icecast -c ./icecast.xml 2> /dev/null &
+ICECAST_PID=$!
+sleep 5
+
+echo "# Starting Source client on /$MOUNT_LISTENER_AUTH"
+ffmpeg -loglevel panic -re -f lavfi -i "sine=frequency=1000" -content_type application/ogg "icecast://$AUTH_SOURCE@127.0.0.1:8000/$MOUNT_LISTENER_AUTH" &
+SOURCE1_PID=$!
+
+echo "# Starting Source client on /$MOUNT_SOURCE_AUTH"
+ffmpeg -loglevel panic -re -f lavfi -i "sine=frequency=1000" -content_type application/ogg "icecast://$AUTH_MOUNT@127.0.0.1:8000/$MOUNT_SOURCE_AUTH" &
+SOURCE2_PID=$!
+
+function test_endpoint {
+ echo "# CURL $2"
+ if test "x$4" == "x"; then
+ res=$(curl -m 5 -o /dev/null -D - "$ICECAST_BASE_URL$2" 2>/dev/null | head -n 1 | cut -d$' ' -f2- | tr -d '\r\n')
+ else
+ res=$(curl -m 5 -u "$4" -o /dev/null -D - "$ICECAST_BASE_URL$2" 2>/dev/null | head -n 1 | cut -d$' ' -f2- | tr -d '\r\n')
+ fi
+ if test "$(echo "$res" | cut -d$' ' -f1)" -eq "$3"; then
+ echo "# OK [$res]"
+ echo "ok $counter - $1"
+ else
+ echo " FAIL [$res] Expected: $3"
+ echo "not ok $counter - $1"
+ fi
+ ((counter++))
+}
+
+function test_sourcing {
+ echo "# CURL $2"
+ if test "x$4" == "x"; then
+ res=$(curl -X PUT -m 5 -d /dev/random -o /dev/null -D - "$ICECAST_BASE_URL$2" 2>/dev/null | head -n 1 | cut -d$' ' -f2- | tr -d '\r\n')
+ else
+ res=$(dd if=/dev/urandom bs=8 count=32 2>/dev/null | curl -X PUT -m 5 -H "Content-Type: application/octet-stream" --data-binary "@-" -v -u "$4" -o /dev/null -D - "$ICECAST_BASE_URL$2" 2>/dev/null | head -n 1 | cut -d$' ' -f2- | tr -d '\r\n')
+ fi
+ if test "$(echo "$res" | cut -d$' ' -f1)" -eq "$3"; then
+ echo "# OK [$res]"
+ echo "ok $counter - $1"
+ else
+ echo "# FAIL [$res] Expected: $3"
+ echo "not ok $counter - $1"
+ fi
+ ((counter++))
+}
+
+echo "#"
+echo "# Testing admin/manageauth endpoint"
+test_endpoint "manageauth-add-noauth" "admin/manageauth?id=4&username=$L_USER&password=$L_PASS&action=add" 401
+test_endpoint "manageauth-add-invalid-noauth" "admin/manageauth?id=99&username=$L_USER&password=$L_PASS&action=add" 401
+test_endpoint "manageauth-add-invalid" "admin/manageauth?id=99&username=$L_USER&password=$L_PASS&action=add" 404 "$AUTH_ADMIN"
+test_endpoint "manageauth-add-valid" "admin/manageauth?id=4&username=$L_USER&password=$L_PASS&action=add" 200 "$AUTH_ADMIN"
+
+echo "#"
+echo "# Testing admin/buildm3u endpoint"
+test_endpoint "buildm3u-user" "admin/buildm3u?username=$L_USER&password=$L_PASS&mount=%2F$MOUNT_LISTENER_AUTH" 200
+test_endpoint "buildm3u-fakeuser" "admin/buildm3u?username=something&password=foo&mount=%2F$MOUNT_LISTENER_AUTH" 200
+
+echo "#"
+echo "# Testing admin/stats endpoint"
+test_endpoint "stats-noauth" "admin/stats" 401
+test_endpoint "stats-sourceauth" "admin/stats" 401 "$AUTH_SOURCE"
+test_endpoint "stats-listenerauth" "admin/stats" 401 "$L_AUTH"
+test_endpoint "stats-adminauth" "admin/stats" 200 "$AUTH_ADMIN"
+
+echo "#"
+echo "# Testing admin/listclients endpoint"
+test_endpoint "listclients-noauth" "admin/listclients" 401
+test_endpoint "listclients-sourceauth" "admin/listclients" 401 "$AUTH_SOURCE"
+test_endpoint "listclients-listenerauth" "admin/listclients" 401 "$L_AUTH"
+test_endpoint "listclients-adminauth-invalid" "admin/listclients" 400 "$AUTH_ADMIN"
+test_endpoint "listclients-adminauth" "admin/listclients?mount=%2F$MOUNT_LISTENER_AUTH" 200 "$AUTH_ADMIN"
+
+echo "#"
+echo "# Testing admin/moveclients endpoint"
+test_endpoint "moveclients-noauth" "admin/moveclients" 401
+test_endpoint "moveclients-sourceauth" "admin/moveclients" 401 "$AUTH_SOURCE"
+test_endpoint "moveclients-listenerauth" "admin/moveclients" 401 "$L_AUTH"
+test_endpoint "moveclients-adminauth-invalid" "admin/moveclients" 400 "$AUTH_ADMIN"
+test_endpoint "moveclients-adminauth" "admin/moveclients?mount=%2F$MOUNT_LISTENER_AUTH" 200 "$AUTH_ADMIN"
+
+echo "#"
+echo "# Testing admin/updatemetadata endpoint"
+test_endpoint "updatemetadata-noauth" "admin/updatemetadata" 401
+test_endpoint "updatemetadata-sourceauth" "admin/updatemetadata" 401 "$AUTH_SOURCE"
+test_endpoint "updatemetadata-listenerauth" "admin/updatemetadata" 401 "$L_AUTH"
+test_endpoint "updatemetadata-adminauth-invalid" "admin/updatemetadata" 400 "$AUTH_ADMIN"
+test_endpoint "updatemetadata-adminauth" "admin/updatemetadata?mount=%2F$MOUNT_LISTENER_AUTH" 200 "$AUTH_ADMIN"
+
+echo "#"
+echo "# Testing admin/metadata endpoint"
+test_endpoint "metadata-noauth" "admin/metadata" 401
+test_endpoint "metadata-sourceauth" "admin/metadata" 401 "$AUTH_SOURCE"
+test_endpoint "metadata-listenerauth" "admin/metadata" 401 "$L_AUTH"
+test_endpoint "metadata-adminauth-invalid" "admin/metadata" 400 "$AUTH_ADMIN"
+test_endpoint "metadata-sourceauth" "admin/metadata?mount=%2F$MOUNT_LISTENER_AUTH&mode=updinfo&charset=UTF-8&song=Test1" 200 "$AUTH_SOURCE"
+test_endpoint "metadata-adminauth" "admin/metadata?mount=%2F$MOUNT_LISTENER_AUTH&mode=updinfo&charset=UTF-8&song=Test2" 200 "$AUTH_ADMIN"
+test_endpoint "metadata-sourceauth" "admin/metadata?mount=%2F$MOUNT_SOURCE_AUTH&mode=updinfo&charset=UTF-8&song=Test1" 401 "$AUTH_SOURCE"
+test_endpoint "metadata-adminauth" "admin/metadata?mount=%2F$MOUNT_SOURCE_AUTH&mode=updinfo&charset=UTF-8&song=Test2" 200 "$AUTH_ADMIN"
+test_endpoint "metadata-mountauth" "admin/metadata?mount=%2F$MOUNT_SOURCE_AUTH&mode=updinfo&charset=UTF-8&song=Test3" 200 "$AUTH_MOUNT"
+
+echo "#"
+echo "# Testing admin/listmounts endpoint"
+test_endpoint "listmounts-noauth" "admin/listmounts" 401
+test_endpoint "listmounts-sourceauth" "admin/listmounts" 401 "$AUTH_SOURCE"
+test_endpoint "listmounts-listenerauth" "admin/listmounts" 401 "$L_AUTH"
+test_endpoint "listmounts-adminauth" "admin/listmounts" 200 "$AUTH_ADMIN"
+test_endpoint "listmounts-adminauth-mount" "admin/listmounts?mount=%2F$MOUNT_LISTENER_AUTH" 400 "$AUTH_ADMIN"
+
+echo "#"
+echo "# Testing mountpoint which requires auth"
+test_endpoint "authmount-noauth" "$MOUNT_LISTENER_AUTH" 401
+test_endpoint "authmount-sourceauth" "$MOUNT_LISTENER_AUTH" 401 "$AUTH_SOURCE"
+test_endpoint "authmount-listenerauth" "$MOUNT_LISTENER_AUTH" 200 "$L_AUTH"
+test_endpoint "authmount-adminauth" "$MOUNT_LISTENER_AUTH" 401 "$AUTH_ADMIN"
+
+echo "#"
+echo "# Testing mountpoint which doesn't require auth"
+test_endpoint "mount-noauth" "$MOUNT_SOURCE_AUTH" 200
+test_endpoint "mount-sourceauth" "$MOUNT_SOURCE_AUTH" 200 "$AUTH_SOURCE"
+test_endpoint "mount-listenerauth" "$MOUNT_SOURCE_AUTH" 200 "$L_AUTH"
+test_endpoint "mount-adminauth" "$MOUNT_SOURCE_AUTH" 200 "$AUTH_ADMIN"
+
+echo "#"
+echo "# Testing admin/killsource endpoint"
+test_endpoint "killsource-noauth" "admin/killsource" 401
+test_endpoint "killsource-sourceauth" "admin/killsource" 401 "$AUTH_SOURCE"
+test_endpoint "killsource-listenerauth" "admin/killsource" 401 "$L_AUTH"
+test_endpoint "killsource-adminauth-invalid" "admin/killsource" 400 "$AUTH_ADMIN"
+test_endpoint "killsource-adminauth" "admin/killsource?mount=%2F$MOUNT_LISTENER_AUTH" 200 "$AUTH_ADMIN"
+
+echo "#"
+echo "# Testing on-connect handling with probing"
+test_sourcing "on-connect-test-noauth" "test-on-connect.ogg" 401
+if [ -f "on-connect-success" ]; then
+ echo "# FAIL: on-connect triggered, even though it should not!"
+ rm "on-connect-success"
+fi
+test_sourcing "on-connect-test-sourceauth" "test-on-connect.ogg" 200 "$AUTH_SOURCE"
+if [ -f "on-connect-success" ]; then
+ echo "# OK: on-connect triggered!"
+ rm "on-connect-success"
+else
+ echo "# FAIL: on-connect not triggered!"
+fi
+
+echo "#"
+echo "# All tests done, cleanup..."
+echo "#"
+
+rm "myauth"
+
+if kill $SOURCE1_PID > /dev/null 2>&1; then
+ echo "# Terminated SOURCE1"
+fi
+if kill $SOURCE2_PID > /dev/null 2>&1; then
+ echo "# Terminated SOURCE2"
+fi
+
+if kill $ICECAST_PID > /dev/null 2>&1; then
+ echo "# Terminated Icecast"
+fi
+
+echo "1..$(((counter-1)))" # Number of tests to be executed.
+exit 0
diff --git a/tests/icecast.xml b/tests/icecast.xml
index 3e0503f5..d1b4a313 100644
--- a/tests/icecast.xml
+++ b/tests/icecast.xml
@@ -41,6 +41,11 @@
hackmemore
+
+ /test-on-connect.ogg
+ on-connect.sh
+
+
/foo.ogg
diff --git a/tests/on-connect.sh b/tests/on-connect.sh
new file mode 100755
index 00000000..3a288f1b
--- /dev/null
+++ b/tests/on-connect.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+touch "on-connect-success"
+exit 0
diff --git a/tests/startup.test b/tests/startup.test
new file mode 100755
index 00000000..720a9c08
--- /dev/null
+++ b/tests/startup.test
@@ -0,0 +1,19 @@
+#!/bin/sh
+../src/icecast -c ./icecast.xml 2> /dev/null &
+ICECAST_PID=$!
+echo 'ok 1 - Icecast started'
+sleep 3
+
+if kill -0 $ICECAST_PID > /dev/null 2>&1; then
+ echo 'ok 2 - Icecast running'
+else
+ echo 'not ok 2 - Icecast not running'
+fi
+
+if kill $ICECAST_PID > /dev/null 2>&1; then
+ echo 'ok 3 - Icecast stopped'
+else
+ echo 'not ok 3 - Icecast not stopped'
+fi
+
+echo 1..3 # Number of tests to be executed.