diff --git a/docker/tests/conftest.py b/docker/tests/conftest.py index 7195d07..4d54232 100644 --- a/docker/tests/conftest.py +++ b/docker/tests/conftest.py @@ -1,165 +1,165 @@ # Copyright (C) 2019-2021 The Software Heritage developers # See the AUTHORS file at the top-level directory of this distribution # License: GNU General Public License version 3, or any later version # See top-level LICENSE file for more information from os.path import join import re import subprocess import time from typing import Generator, Mapping, Tuple from urllib.parse import urljoin import pytest import requests import testinfra APIURL = "http://127.0.0.1:5080/api/1/" SAMPLE_METADATA = """\ Test Software swh test-software No One """ # scope='session' so we use the same container for all the tests; @pytest.fixture(scope="session") def docker_compose(request): # start the whole cluster subprocess.check_output(["docker-compose", "up", "-d"]) yield # and strop it - subprocess.check_call(["docker-compose", "down"]) + subprocess.check_call(["docker-compose", "down", "-v"]) @pytest.fixture(scope="session") def wfi_timeout(): """ wait-for-it timeout in seconds """ return 60 @pytest.fixture(scope="session") def scheduler_host(request, docker_compose, wfi_timeout): # run a container in which test commands are executed docker_id = ( subprocess.check_output( ["docker-compose", "run", "-d", "swh-scheduler", "shell", "sleep", "1h"] ) .decode() .strip() ) scheduler_host = testinfra.get_host("docker://" + docker_id) scheduler_host.check_output(f"wait-for-it swh-scheduler:5008 -t {wfi_timeout}") scheduler_host.check_output(f"wait-for-it swh-storage:5002 -t {wfi_timeout}") # return a testinfra connection to the container yield scheduler_host # at the end of the test suite, destroy the container subprocess.check_call(["docker", "rm", "-f", docker_id]) # scope='session' so we use the same container for all the tests; @pytest.fixture(scope="session") def deposit_host(request, docker_compose, wfi_timeout): # run a container in which test commands are executed docker_id = ( subprocess.check_output( ["docker-compose", "run", "-d", "swh-deposit", "shell", "sleep", "1h"] ) .decode() .strip() ) deposit_host = testinfra.get_host("docker://" + docker_id) deposit_host.check_output("echo 'print(\"Hello World!\")\n' > /tmp/hello.py") deposit_host.check_output("tar -C /tmp -czf /tmp/archive.tgz /tmp/hello.py") deposit_host.check_output(f"echo '{SAMPLE_METADATA}' > /tmp/metadata.xml") deposit_host.check_output(f"wait-for-it swh-deposit:5006 -t {wfi_timeout}") # return a testinfra connection to the container yield deposit_host # at the end of the test suite, destroy the container subprocess.check_call(["docker", "rm", "-f", docker_id]) @pytest.fixture(scope="session") def git_url(): return "https://forge.softwareheritage.org/source/swh-core" @pytest.fixture(scope="session") def git_origin(scheduler_host, git_url): task = scheduler_host.check_output(f"swh scheduler task add load-git url={git_url}") taskid = re.search(r"^Task (?P\d+)$", task, flags=re.MULTILINE).group("id") assert int(taskid) > 0 for i in range(60): status = scheduler_host.check_output( f"swh scheduler task list --list-runs --task-id {taskid}" ) if "Executions:" in status: if "[eventful]" in status: break if "[started]" in status or "[scheduled]" in status: time.sleep(1) continue if "[failed]" in status: loader_logs = subprocess.check_output( ["docker-compose", "logs", "swh-loader"] ) assert False, ( "Loading execution failed\n" f"status: {status}\n" f"loader logs: " + loader_logs.decode(errors="replace") ) assert False, f"Loading execution failed, task status is {status}" return git_url # Utility functions def apiget(path: str, verb: str = "GET", **kwargs): """Query the API at path and return the json result or raise an AssertionError""" url = urljoin(APIURL, path) resp = requests.request(verb, url, **kwargs) assert resp.status_code == 200, f"failed to retrieve {url}: {resp.text}" return resp.json() def pollapi(path: str, verb: str = "GET", **kwargs): """Poll the API at path until it returns an OK result""" url = urljoin(APIURL, path) for i in range(60): resp = requests.request(verb, url, **kwargs) if resp.ok: break time.sleep(1) else: assert False, f"Polling {url} failed" return resp def getdirectory( dirid: str, currentpath: str = "" ) -> Generator[Tuple[str, Mapping], None, None]: """Recursively retrieve directory description from the archive""" directory = apiget(f"directory/{dirid}") for direntry in directory: path = join(currentpath, direntry["name"]) if direntry["type"] != "dir": yield (path, direntry) else: yield from getdirectory(direntry["target"], path) diff --git a/docker/tests/run_tests.sh b/docker/tests/run_tests.sh index 2cdbbdd..b24944e 100755 --- a/docker/tests/run_tests.sh +++ b/docker/tests/run_tests.sh @@ -1,185 +1,185 @@ #!/bin/bash # Main script to run high level tests on the Software Heritage stack # Use a temporary directory as working directory WORKDIR=/tmp/swh-docker-dev_tests # Create it if it does not exist mkdir $WORKDIR 2>/dev/null # Ensure it is empty before running the tests rm -rf $WORKDIR/* # We want the script to exit at the first encountered error set -e # Get test scripts directory TEST_SCRIPTS_DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd) # Set the docker-compose.yml file to use export COMPOSE_FILE=$TEST_SCRIPTS_DIR/../docker-compose.yml # Useful global variables SWH_WEB_API_BASEURL="http://localhost:5080/api/1" CURRENT_TEST_SCRIPT="" # Colored output related variables and functions (only if stdout is a terminal) if test -t 1; then GREEN='\033[0;32m' RED='\033[0;31m' NC='\033[0m' else DOCO_OPTIONS='--ansi never' fi # Remove previously dumped service logs file if any rm -f $TEST_SCRIPTS_DIR/swh-docker-compose.logs function colored_output { local msg="$2" if [ "$CURRENT_TEST_SCRIPT" != "" ]; then msg="[$CURRENT_TEST_SCRIPT] $msg" fi echo -e "${1}${msg}${NC}" } function status_message { colored_output ${GREEN} "$1" } function error_message { colored_output ${RED} "$1" } function dump_docker_logs { error_message "Dumping logs for all services in file $TEST_SCRIPTS_DIR/swh-docker-compose.logs" docker-compose logs > $TEST_SCRIPTS_DIR/swh-docker-compose.logs } # Exit handler that will get called when this script terminates function finish { if [ $? -ne 0 ] && [ "$CURRENT_TEST_SCRIPT" != "" ]; then local SCRIPT_NAME=$CURRENT_TEST_SCRIPT CURRENT_TEST_SCRIPT="" error_message "An error occurred when running test script ${SCRIPT_NAME}" dump_docker_logs fi - docker-compose $DOCO_OPTIONS down + docker-compose $DOCO_OPTIONS down -v rm -rf $WORKDIR } trap finish EXIT # Docker-compose events listener that will be executed in background # Parameters: # $1: PID of parent process function listen_docker_events { docker-compose $DOCO_OPTIONS events | while read event do service=$(echo $event | cut -d " " -f7 | sed 's/^name=swh-docker-dev_\(.*\)_1)/\1/') event_type=$(echo $event | cut -d ' ' -f4) # "docker-compose down" has been called, exiting this child process if [ "$event_type" = "kill" ] ; then exit # a swh service crashed, sending signal to parent process to exit with error elif [ "$event_type" = "die" ]; then if [[ "$service" =~ ^swh.* ]]; then exit_code=$(docker-compose ps | grep $service | awk '{print $4}') if [ "$exit_code" != "0" ]; then error_message "Service $service died unexpectedly, exiting" dump_docker_logs kill -s SIGUSR1 $1; exit fi fi fi done } trap "exit 1" SIGUSR1 declare -A SERVICE_LOGS_NB_LINES_READ # Function to wait for a specific string to be outputted in a specific # docker-compose service logs. # When called multiple times on the same service, only the newly outputted # logs since the last call will be processed. # Parameters: # $1: a timeout value in seconds to stop waiting and exit with error # $2: docker-compose service name # $3: the string to look for in the produced logs function wait_for_service_output { local nb_lines_to_skip=0 if [[ -v "SERVICE_LOGS_NB_LINES_READ[$2]" ]]; then let nb_lines_to_skip=${SERVICE_LOGS_NB_LINES_READ[$2]}+1 fi SECONDS=0 local service_logs=$(docker-compose $DOCO_OPTIONS logs $2 | tail -n +$nb_lines_to_skip) until echo -ne "$service_logs" | grep -m 1 "$3" >/dev/null ; do sleep 1; if (( $SECONDS > $1 )); then error_message "Could not find pattern \"$3\" in $2 service logs after $1 seconds" exit 1 fi let nb_lines_to_skip+=$(echo -ne "$service_logs" | wc -l) service_logs=$(docker-compose $DOCO_OPTIONS logs $2 | tail -n +$nb_lines_to_skip) done let nb_lines_to_skip+=$(echo -ne "$service_logs" | wc -l) SERVICE_LOGS_NB_LINES_READ[$2]=$nb_lines_to_skip } # Function to make an HTTP request and gets its response. # It should be used the following way: # response=$(http_request ) # Parameters: # $1: http method name (GET, POST, ...) # $2: request url function http_request { local response=$(curl -sS -X $1 $2) echo $response } # Function to check that an HTTP request ends up with no errors. # If the HTTP response code is different from 200, an error will # be raised and the main script will terminate # Parameters: # $1: http method name (GET, POST, ...) # $2: request url function http_request_check { curl -sSf -X $1 $2 > /dev/null } # Function to run the content of a script dedicated to test a specific # part of the Software Heritage stack. function run_test_script { local SCRIPT_NAME=$(basename $1) status_message "Executing test script $SCRIPT_NAME" CURRENT_TEST_SCRIPT=$SCRIPT_NAME source $1 } # Move to work directory cd $WORKDIR # Start the docker-compose event handler as a background process status_message "Starting docker-compose events listener" listen_docker_events $$ & # Start the docker-compose environment including the full Software Heritage stack status_message "Starting swh docker-compose environment" docker-compose $DOCO_OPTIONS up -d # Print logs to stdout docker-compose $DOCO_OPTIONS logs -f & # Ensure all swh services are up before running tests status_message "Waiting for swh services to be up" docker-compose $DOCO_OPTIONS exec -T swh-storage wait-for-it localhost:5002 -s --timeout=0 docker-compose $DOCO_OPTIONS exec -T swh-objstorage wait-for-it localhost:5003 -s --timeout=0 docker-compose $DOCO_OPTIONS exec -T swh-web wait-for-it localhost:5004 -s --timeout=0 docker-compose $DOCO_OPTIONS exec -T swh-vault wait-for-it localhost:5005 -s --timeout=0 docker-compose $DOCO_OPTIONS exec -T swh-deposit wait-for-it localhost:5006 -s --timeout=0 docker-compose $DOCO_OPTIONS exec -T swh-idx-storage wait-for-it localhost:5007 -s --timeout=0 docker-compose $DOCO_OPTIONS exec -T swh-scheduler wait-for-it localhost:5008 -s --timeout=0 # Execute test scripts for test_script in $TEST_SCRIPTS_DIR/test_*.sh; do run_test_script ${test_script} CURRENT_TEST_SCRIPT="" done