bats: postgres tests (#1377)

* split CI/bats in sqlite+mysql+mariadb+hub
* renamed db and user 'crowdsec' to 'crowdsec-test'; moved backend-specific scripts to lib/db, etc.
* postgres tests
* force delay between ipv6 tests (for myisam)
* force delay after pg_restore to ensure data is written
This commit is contained in:
mmetc 2022-03-21 15:51:05 +01:00 committed by GitHub
parent 411baa4dcf
commit 0667552132
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 405 additions and 207 deletions

View file

@ -1,4 +1,4 @@
name: BATS functional tests
name: bats / hub tests
on:
push:
@ -35,8 +35,8 @@ jobs:
- name: "BATS: build crowdsec"
run: make bats-clean bats-build
- name: "BATS: run tests"
run: make bats-test
- name: "BATS: run hub tests"
run: make bats-test-hub
- name: "BATS: collect hub coverage"
run: ./tests/collect-hub-coverage >> $GITHUB_ENV

58
.github/workflows/ci_bats_mariadb.yaml vendored Normal file
View file

@ -0,0 +1,58 @@
name: bats / functional tests with MariaDB
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
name: "Build the application"
runs-on: ubuntu-latest
timeout-minutes: 20
services:
mariadb:
image: mariadb:latest
env:
MYSQL_ROOT_PASSWORD: mysql
ports:
- 3306:3306
steps:
- name: "Set up Go 1.17"
uses: actions/setup-go@v1
with:
go-version: 1.17
id: go
- name: "Clone CrowdSec"
uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: true
- name: "Install bats dependencies"
run: |
sudo apt install daemonize netcat-openbsd
GO111MODULE=on go get github.com/mikefarah/yq/v4
- name: "BATS: build crowdsec"
run: make bats-clean bats-build
env:
DB_BACKEND: mysql
MYSQL_HOST: 127.0.0.1
MYSQL_PORT: 3306
MYSQL_PASSWORD: mysql
MYSQL_USER: root
- name: "BATS: run tests"
run: make bats-test
env:
DB_BACKEND: mysql
MYSQL_HOST: 127.0.0.1
MYSQL_PORT: 3306
MYSQL_PASSWORD: mysql
MYSQL_USER: root

View file

@ -1,4 +1,4 @@
name: BATS functional tests with MYSQL
name: bats / functional tests with MySQL
on:
push:

63
.github/workflows/ci_bats_postgres.yaml vendored Normal file
View file

@ -0,0 +1,63 @@
name: bats / functional tests with PostgreSQL
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
name: "Build the application"
runs-on: ubuntu-latest
timeout-minutes: 20
services:
postgres:
image: postgres:latest
env:
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: "Set up Go 1.17"
uses: actions/setup-go@v1
with:
go-version: 1.17
id: go
- name: "Clone CrowdSec"
uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: true
- name: "Install bats dependencies"
run: |
sudo apt install daemonize netcat-openbsd
GO111MODULE=on go get github.com/mikefarah/yq/v4
- name: "BATS: build crowdsec"
run: make bats-clean bats-build
env:
DB_BACKEND: postgres
POSTGRES_HOST: 127.0.0.1
POSTGRES_PORT: 5432
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
- name: "BATS: run tests"
run: make bats-test
env:
DB_BACKEND: postgres
POSTGRES_HOST: 127.0.0.1
POSTGRES_PORT: 5432
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres

39
.github/workflows/ci_bats_sqlite.yaml vendored Normal file
View file

@ -0,0 +1,39 @@
name: bats / functional tests with sqlite
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
name: "Build the application"
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: "Set up Go 1.17"
uses: actions/setup-go@v1
with:
go-version: 1.17
id: go
- name: "Clone CrowdSec"
uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: true
- name: "Install bats dependencies"
run: |
sudo apt install daemonize netcat-openbsd
GO111MODULE=on go get github.com/mikefarah/yq/v4
- name: "BATS: build crowdsec"
run: make bats-clean bats-build
- name: "BATS: run tests"
run: make bats-test

View file

@ -1,4 +1,4 @@
name: tests
name: Go tests
#those env variables are for localstack, so we can emulate aws services
env:

View file

@ -13,7 +13,7 @@ LOCAL_INIT_DIR = $(TEST_DIR)/local-init
LOG_DIR = $(LOCAL_DIR)/var/log
PID_DIR = $(LOCAL_DIR)/var/run
PLUGIN_DIR = $(LOCAL_DIR)/lib/crowdsec/plugins
DB_BACKEND := $(or $(DB_BACKEND), "sqlite")
DB_BACKEND ?= "sqlite"
define ENV :=
export TEST_DIR="$(TEST_DIR)"
@ -28,28 +28,24 @@ export PLUGIN_DIR="$(PLUGIN_DIR)"
export DB_BACKEND="$(DB_BACKEND)"
endef
bats-all: bats-clean bats-build bats-test
bats-all: bats-clean bats-build bats-test bats-test-hub
# Source this to run the scripts outside of the Makefile
tests/.environment.sh:
bats-environment:
$(file >$(TEST_DIR)/.environment.sh,$(ENV))
bats-environment: tests/.environment.sh
# Verify dependencies and submodules
bats-check-requirements:
@$(TEST_DIR)/check-requirements
# Build and installs crowdsec in a local directory
# Create a reusable package with initial configuration + data
# Generate dynamic tests
bats-build: bats-environment bats-check-requirements
@DEFAULT_CONFIGDIR=$(CONFIG_DIR) DEFAULT_DATADIR=$(DATA_DIR) $(MAKE) build
@mkdir -p $(BIN_DIR) $(LOG_DIR) $(PID_DIR) $(PLUGIN_DIR)
@install -m 0755 cmd/crowdsec/crowdsec cmd/crowdsec-cli/cscli $(BIN_DIR)/
@install -m 0755 plugins/notifications/*/notification-* $(PLUGIN_DIR)/
@$(TEST_DIR)/instance-data make
@$(TEST_DIR)/generate-hub-tests
# Remove the local crowdsec installation and the fixture config + data
bats-clean:
@ -57,7 +53,12 @@ bats-clean:
# Run the test suite
bats-test: bats-environment bats-check-requirements
$(TEST_DIR)/run-tests
$(TEST_DIR)/run-tests $(TEST_DIR)/bats
# Generate dynamic tests
bats-test-hub: bats-environment bats-check-requirements
@$(TEST_DIR)/generate-hub-tests
$(TEST_DIR)/run-tests $(TEST_DIR)/dyn-bats
# Static checks for the test scripts.
# Not failproof but they can catch bugs and improve learning of sh/bash

View file

@ -21,6 +21,11 @@ setup() {
load "../lib/setup.sh"
}
teardown() {
# XXX myisam stopgap
sleep 0.3
}
#----------
api() {

View file

@ -21,6 +21,11 @@ setup() {
load "../lib/setup.sh"
}
teardown() {
# XXX myisam stopgap
sleep 0.3
}
#----------
api() {

109
tests/fixtures/mysql vendored
View file

@ -1,109 +0,0 @@
#!/usr/bin/env bash
set -eu
script_name=$0
die() {
echo >&2 "$@"
exit 1
}
MYSQL_HOST=${MYSQL_HOST:-127.0.0.1}
MYSQL_PORT=${MYSQL_PORT:-3306}
MYSQL_PASSWORD=${MYSQL_PASSWORD:-password}
MYSQL_USER=${MYSQL_USER:-root}
about() {
die "usage: $0 [ setup | cleanup | configure | backup | restore]"
}
check_mysql_client() {
if ! command -v mysql >/dev/null; then
die "missing required program 'mysql' as a mysql client (package mariadb-client-core-10.6 on debian like system)"
fi
}
send_mysql() {
if [ "$#" -lt 1 ]; then
die "missing requirement argument to send the mysql database"
fi
/bin/echo "$1" | mysql -h ${MYSQL_HOST} -u ${MYSQL_USER} --port=${MYSQL_PORT} -p${MYSQL_PASSWORD}
}
requirements() {
check_mysql_client
}
setup_database() {
send_mysql "CREATE DATABASE IF NOT EXISTS crowdsec;"
send_mysql "CREATE USER IF NOT EXISTS 'crowdsec' IDENTIFIED BY 'crowdsec';"
send_mysql "GRANT ALL PRIVILEGES ON crowdsec.* TO 'crowdsec';"
}
# cleanup is idempotent
cleanup_database() {
send_mysql "DROP DATABASE IF EXISTS crowdsec;"
send_mysql "DROP USER IF EXISTS crowdsec;"
}
backup_database() {
if [ "$#" -lt 1 ]; then
die "missing file to backup database to"
fi
mysqldump -h${MYSQL_HOST} -u${MYSQL_USER} -p${MYSQL_PASSWORD} --databases crowdsec > $1
}
restore_database() {
if [ "$#" -lt 1 ]; then
die "missing file to restore database to"
fi
if [ -f $1 ]; then
mysql -h ${MYSQL_HOST} -u ${MYSQL_USER} --port=${MYSQL_PORT} -p${MYSQL_PASSWORD} < $1
else
die "$2 doesn\'t exist"
fi
send_mysql "CREATE USER IF NOT EXISTS 'crowdsec' IDENTIFIED BY 'crowdsec';"
send_mysql "GRANT ALL PRIVILEGES ON crowdsec.* TO 'crowdsec';"
}
setup_configuration() {
MYSQL_PORT=${MYSQL_PORT} MYSQL_HOST=${MYSQL_HOST} yq '
.db_config.type="mysql"|
.db_config.user="crowdsec" |
.db_config.password="crowdsec" |
.db_config.db_name="crowdsec" |
.db_config.host=strenv(MYSQL_HOST) |
.db_config.port=env(MYSQL_PORT) |
del(.db_config.db_path)
' -i "${CONFIG_YAML}"
}
case "$1" in
setup)
setup_database
;;
cleanup)
cleanup_database
;;
configure)
setup_configuration
;;
backup)
if [ "$#" -lt 2 ]; then
die "missing file to restore backup to"
fi
backup_database $2
;;
restore)
if [ "$#" -lt 2 ]; then
die "missing file to restore restore to"
fi
restore_database $2
;;
*)
about
;;
esac;

View file

@ -41,8 +41,6 @@ export CONFIG_DIR
remove_init_data() {
rm -rf -- "${LOCAL_DIR:?}/${REL_CONFIG_DIR}"/* "${LOCAL_DIR:?}/${REL_DATA_DIR:?}"/*
./instance-db cleanup
}
config_generate() {
@ -83,18 +81,18 @@ make_init_data() {
mkdir -p "${DATA_DIR}"
mkdir -p "${CONFIG_DIR}/notifications"
mkdir -p "${CONFIG_DIR}/hub"
mkdir -p "${CONFIG_DIR}/patterns"
cp -ax "../config/patterns" "${CONFIG_DIR}/"
config_generate
./instance-db configure
# XXX errors from instance-db should be reported...
./instance-db config-yaml
./instance-db setup
mkdir -p "${CONFIG_DIR}/hub"
"${CSCLI}" machines add githubciXXXXXXXXXXXXXXXXXXXXXXXX --auto
"${CSCLI}" capi register
"${CSCLI}" hub update
"${CSCLI}" collections install crowdsecurity/linux
mkdir -p "${CONFIG_DIR}/patterns"
cp -ax "../config/patterns" "${CONFIG_DIR}/"
"${TEST_DIR}/instance-crowdsec" start
"${CSCLI}" lapi status
@ -104,7 +102,8 @@ make_init_data() {
mkdir -p "${LOCAL_INIT_DIR}"
./instance-db backup "${LOCAL_INIT_DIR}/database"
./instance-db dump "${LOCAL_INIT_DIR}/database"
echo "${DB_BACKEND}" > "${LOCAL_INIT_DIR}/.backend"
tar -C "${LOCAL_DIR}" --create \
--exclude "$REL_DATA_DIR"/crowdsec.db \
@ -118,6 +117,11 @@ load_init_data() {
die "Initial data not found; did you run '$script_name make' ?"
fi
dump_backend="$(cat "${LOCAL_INIT_DIR}/.backend")"
if [ "$DB_BACKEND" != "$dump_backend" ]; then
die "Can't run with backend '$DB_BACKEND' because the test data was build with '$dump_backend'"
fi
remove_init_data
tar -C "${LOCAL_DIR}" --extract --file "${LOCAL_INIT_DIR}/init-config-data.tar"

View file

@ -3,16 +3,16 @@
#shellcheck disable=SC1007
THIS_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
cd "${THIS_DIR}"
#shellcheck disable=SC1090
. ./.environment.sh
case "$DB_BACKEND" in
sqlite)
./instance-sqlite "$@"
;;
mysql)
./instance-mysql "$@"
;;
*)
echo >&2 "unknown $DB_BACKEND"
exit 1
;;
esac;
./assert-crowdsec-not-running
backend_script="./lib/db/instance-${DB_BACKEND}"
if [ ! -x "$backend_script" ]; then
echo "unknown database '$DB_BACKEND'" >&2
exit 1
fi
exec "$backend_script" "$@"

View file

@ -1,56 +0,0 @@
#!/usr/bin/env bash
set -eu
script_name=$0
die() {
echo >&2 "$@"
exit 1
}
about() {
die "usage: $script_name [backup | restore | configure | cleanup | setup] <backup_file>"
}
#shellcheck disable=SC1007
THIS_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
cd "${THIS_DIR}"
#shellcheck disable=SC1090
. ./.environment.sh
# you have not removed set -u above, have you?
[ -z "${DATA_DIR-}" ] && die "\$DATA_DIR must be defined."
# ---------------------------
[ $# -lt 1 ] && about
./assert-crowdsec-not-running
case "$1" in
backup)
[ $# -lt 2 ] && about
backup_file="$2"
# dirty fast cp. nothing should be accessing it right now, anyway.
fixtures/mysql backup $backup_file
;;
restore)
[ $# -lt 2 ] && about
backup_file="$2"
[ -f "$backup_file" ] || die "missing file $backup_file"
fixtures/mysql restore $backup_file
;;
configure)
fixtures/mysql configure
;;
setup)
fixtures/mysql setup
;;
cleanup)
fixtures/mysql cleanup
;;
*)
about
;;
esac;

92
tests/lib/db/instance-mysql Executable file
View file

@ -0,0 +1,92 @@
#!/usr/bin/env bash
set -eu
script_name=$0
die() {
echo >&2 "$@"
exit 1
}
MYSQL_HOST=${MYSQL_HOST:-127.0.0.1}
MYSQL_PORT=${MYSQL_PORT:-3306}
MYSQL_PASSWORD=${MYSQL_PASSWORD:-password}
MYSQL_USER=${MYSQL_USER:-root}
about() {
die "usage: $script_name [ config_yaml | setup | dump <backup_file> | restore <backup_file> ]"
}
check_mysql_client() {
if ! command -v mysql >/dev/null; then
die "missing required program 'mysql' as a mysql client (package mariadb-client-core-10.6 on debian like system)"
fi
}
exec_sql() {
cmd="${1?Missing required sql command}"
mysql -h "${MYSQL_HOST}" -u "${MYSQL_USER}" "--port=${MYSQL_PORT}" "-p${MYSQL_PASSWORD}" <<< "$cmd"
}
requirements() {
check_mysql_client
}
setup() {
exec_sql "DROP DATABASE IF EXISTS crowdsec_test;"
exec_sql "CREATE DATABASE crowdsec_test;"
exec_sql "DROP USER IF EXISTS crowdsec_test;"
exec_sql "CREATE USER 'crowdsec_test' IDENTIFIED BY 'crowdsec_test';"
exec_sql "GRANT ALL PRIVILEGES ON crowdsec_test.* TO 'crowdsec_test';"
}
dump() {
backup_file="${1?Missing file to backup database to}"
COLUMN_STATISTICS=
if mysqldump --column-statistics 2>&1 | grep -v 'unknown option'; then
COLUMN_STATISTICS='--column-statistics=0'
fi
mysqldump $COLUMN_STATISTICS "-h${MYSQL_HOST}" "-u${MYSQL_USER}" "-p${MYSQL_PASSWORD}" --databases crowdsec_test > "$backup_file"
}
restore() {
backup_file="${1?missing file to restore database from}"
[ -f "$backup_file" ] || die "Backup file $backup_file doesn't exist"
mysql -h "${MYSQL_HOST}" -u "${MYSQL_USER}" "--port=${MYSQL_PORT}" "-p${MYSQL_PASSWORD}" < "$backup_file"
exec_sql "CREATE USER IF NOT EXISTS 'crowdsec_test' IDENTIFIED BY 'crowdsec_test';"
exec_sql "GRANT ALL PRIVILEGES ON crowdsec_test.* TO 'crowdsec_test';"
}
config_yaml() {
MYSQL_PORT=${MYSQL_PORT} MYSQL_HOST=${MYSQL_HOST} yq '
.db_config.type="mysql"|
.db_config.user="crowdsec_test" |
.db_config.password="crowdsec_test" |
.db_config.db_name="crowdsec_test" |
.db_config.host=strenv(MYSQL_HOST) |
.db_config.port=env(MYSQL_PORT) |
del(.db_config.db_path)
' -i "${CONFIG_YAML}"
}
[ $# -lt 1 ] && about
case "$1" in
setup)
setup
;;
config-yaml)
config_yaml
;;
dump)
shift
dump "$@"
;;
restore)
shift
restore "$@"
;;
*)
about
;;
esac;

89
tests/lib/db/instance-postgres Executable file
View file

@ -0,0 +1,89 @@
#!/usr/bin/env bash
set -eu
script_name=$0
die() {
echo >&2 "$@"
exit 1
}
POSTGRES_HOST=${POSTGRES_HOST:-127.0.0.1}
POSTGRES_PORT=${POSTGRES_PORT:-5432}
POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres}
POSTGRES_USER=${POSTGRES_USER:-postgres}
about() {
die "usage: $script_name [ config_yaml | setup | dump <backup_file> | restore <backup_file> ]"
}
check_postgres_client() {
if ! command -v psql >/dev/null; then
die "missing required program 'psql' as a postgres client (package postgres-client-13 on debian like system)"
fi
}
exec_sql() {
cmd="${1?Missing required sql command}"
PGPASSWORD="${POSTGRES_PASSWORD}" psql -h "${POSTGRES_HOST}" --user "${POSTGRES_USER}" "--port=${POSTGRES_PORT}" <<< "$cmd"
}
requirements() {
check_mysql_client
}
setup() {
exec_sql "DROP DATABASE IF EXISTS crowdsec_test;"
exec_sql "CREATE DATABASE crowdsec_test;"
exec_sql "DROP USER IF EXISTS crowdsec_test;"
exec_sql "CREATE USER crowdsec_test WITH ENCRYPTED PASSWORD 'crowdsec_test';"
exec_sql "GRANT ALL PRIVILEGES ON DATABASE crowdsec_test TO crowdsec_test;"
}
dump() {
backup_file="${1?Missing file to backup database to}"
PGPASSWORD="${POSTGRES_PASSWORD}" pg_dump -Ft --host "${POSTGRES_HOST}" --port "${POSTGRES_PORT}" --username "${POSTGRES_USER}" --dbname crowdsec_test > "$backup_file"
}
restore() {
backup_file="${1?missing file to restore database from}"
[ -f "$backup_file" ] || die "Backup file $backup_file doesn't exist"
PGPASSWORD="${POSTGRES_PASSWORD}" pg_restore --username "${POSTGRES_USER}" --host "${POSTGRES_HOST}" --port "${POSTGRES_PORT}" --dbname crowdsec_test --clean < "$backup_file"
# make sure data is ready when crowdsec needs it
sleep 0.3
}
config_yaml() {
POSTGRES_PORT=${POSTGRES_PORT} POSTGRES_HOST=${POSTGRES_HOST} yq '
.db_config.type="postgres"|
.db_config.user="crowdsec_test" |
.db_config.password="crowdsec_test" |
.db_config.db_name="crowdsec_test" |
.db_config.host=strenv(POSTGRES_HOST) |
.db_config.port=env(POSTGRES_PORT) |
.db_config.sslmode="disable" |
del(.db_config.db_path)
' -i "${CONFIG_YAML}"
}
[ $# -lt 1 ] && about
case "$1" in
setup)
setup
;;
config-yaml)
config_yaml
;;
dump)
shift
dump "$@"
;;
restore)
shift
restore "$@"
;;
*)
about
;;
esac;

View file

@ -9,13 +9,13 @@ die() {
}
about() {
die "usage: $script_name [backup | restore] <backup_file>"
die "usage: $script_name [ config-yaml | setup | dump <backup_file> | restore <backup_file> ]"
}
#shellcheck disable=SC1007
THIS_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
cd "${THIS_DIR}"
#shellcheck disable=SC1090
cd "${THIS_DIR}"/../../
#shellcheck disable=SC1091
. ./.environment.sh
# you have not removed set -u above, have you?
@ -26,18 +26,15 @@ cd "${THIS_DIR}"
[ $# -lt 1 ] && about
./assert-crowdsec-not-running
DATA_DIR=$(yq '.config_paths.data_dir' <"${CONFIG_YAML}")
DB_FILE="${DATA_DIR}/crowdsec.db"
case "$1" in
configure)
config-yaml)
;;
setup)
;;
cleanup)
;;
backup)
dump)
[ $# -lt 2 ] && about
backup_file="$2"
# dirty fast cp. nothing should be accessing it right now, anyway.

View file

@ -14,12 +14,22 @@ TEST_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
"${TEST_DIR}/check-requirements"
echo "Running tests..."
echo "DB_BACKEND: $DB_BACKEND"
dump_backend="$(cat "${LOCAL_INIT_DIR}/.backend")"
if [ "$DB_BACKEND" != "$dump_backend" ]; then
die "Can't run with backend '$DB_BACKEND' because the test data was build with '$dump_backend'"
fi
if [ $# -ge 1 ]; then
echo "test files: $*"
"${TEST_DIR}/lib/bats-core/bin/bats" \
--jobs 1 \
--print-output-on-failure \
-T "$@"
else
echo "test files: ${TEST_DIR}/bats ${TEST_DIR}/dyn-bats"
"${TEST_DIR}/lib/bats-core/bin/bats" \
--jobs 1 \
--print-output-on-failure \