5006: Add integration test script and coverage map r=heinrich5991 a=ChillerDragon

The ``scripts/integration_test.sh`` runs two clients and one server. Lets the two clients connect for a few seconds.
The goal is to get some automated runtime tests. Even tho the the two clients do not move they will touch a few gametiles by falling through them. This covers a few basic game mechanics like: freeze, draggers, bullets, solo, shields, tele, speedup, collision, player collision and map finishes.

This pr adds a new directory ``test/`` at the root of the repository. Holding a test map. (not sure if thats cool 🤷 )

The script can be run locally using ``scripts/integration_test.sh [build directory]`` using ``build`` as a default. It will catch client/server crashes and check if sqlite ranks are inserted. If built with asan/ubsan it will catch these errors too. Probably not too useful for local testing since it most likely will not cover the currently developed features.

In the CI it will use a ASan + UBSan build directory and thus catch all basic issues caused by simple  client server interaction.

![image](https://user-images.githubusercontent.com/20344300/163870637-acc5b16d-042b-407a-b223-febcd2565c97.png)


Co-authored-by: Dennis Felsing <dennis@felsin9.de>
Co-authored-by: ChillerDrgon <ChillerDragon@gmail.com>
This commit is contained in:
bors[bot] 2022-05-25 23:44:46 +00:00 committed by GitHub
commit 8e3d5c1b59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 249 additions and 9 deletions

View file

@ -43,3 +43,6 @@ jobs:
cat ./SAN.*
exit 1
fi
- name: Run integration tests with ASan and UBSan
run: |
./scripts/integration_test.sh san

1
.gitignore vendored
View file

@ -78,6 +78,7 @@ generated
target
/build*
/integration_test/*
# IDE project files
.cache

View file

@ -0,0 +1,6 @@
# tests
Use the test script at the root of the repo to run tests
./scripts/integration_test.sh

Binary file not shown.

222
scripts/integration_test.sh Executable file
View file

@ -0,0 +1,222 @@
#!/bin/bash
if [ ! -f ./scripts/integration_test.sh ] || [ ! -f CMakeLists.txt ]
then
echo "Error: make sure your are in the root of the repo"
exit 1
fi
arg_build_dir="build"
arg_end_args=0
arg_verbose=0
for arg in "$@"
do
if [[ "${arg::1}" == "-" ]] && [[ "$arg_end_args" == "0" ]]
then
if [ "$arg" == "-h" ] || [ "$arg" == "--help" ]
then
echo "usage: $(basename "$0") [OPTION..] [build dir]"
echo "description:"
echo " Runs a simple integration test of the client and server"
echo " binaries from the given build dir"
echo "options:"
echo " --help|-h show this help"
echo " --verbose|-v verbose output"
elif [ "$arg" == "-v" ] || [ "$arg" == "--verbose" ]
then
arg_verbose=1
elif [ "$arg" == "--" ]
then
arg_end_args=1
else
echo "Error: unknown arg '$arg'"
fi
else
arg_build_dir="$arg"
fi
done
if [ ! -d "$arg_build_dir" ]
then
echo "Error: build directory '$arg_build_dir' not found"
exit 1
fi
if [ ! -f "$arg_build_dir"/DDNet ]
then
echo "Error: client binary not found '$arg_build_dir/DDNet' not found"
exit 1
fi
if [ ! -f "$arg_build_dir"/DDNet-Server ]
then
echo "Error: server binary not found '$arg_build_dir/DDNet-Server' not found"
exit 1
fi
mkdir -p integration_test
cp "$arg_build_dir"/DDNet* integration_test
cd integration_test || exit 1
function kill_all() {
if [ "$arg_verbose" == "1" ]
then
echo "[*] shutting down test clients and server ..."
fi
sleep 1
echo "shutdown" > server.fifo
echo "quit" > client1.fifo
echo "quit" > client2.fifo
}
got_cleanup=0
function cleanup() {
# needed to fix hang fifo with additional ctrl+c
if [ "$got_cleanup" == "1" ]
then
exit
fi
got_cleanup=1
kill_all
}
trap cleanup EXIT
{
echo $'add_path $CURRENTDIR'
echo $'add_path $USERDIR'
echo $'add_path $DATADIR'
echo $'add_path ../data'
} > storage.cfg
function fail()
{
sleep 1
tail -n2 "$1".log > fail_"$1".txt
echo "$1 exited with code $2" >> fail_"$1".txt
echo "[-] $1 exited with code $2"
}
if test -n "$(find . -maxdepth 1 -name '*.fifo' -print -quit)"
then
rm ./*.fifo
fi
if test -n "$(find . -maxdepth 1 -name 'SAN.*' -print -quit)"
then
rm SAN.*
fi
if test -n "$(find . -maxdepth 1 -name 'fail_*' -print -quit)"
then
rm ./fail_*
fi
if [ -f ddnet-server.sqlite ]
then
rm ddnet-server.sqlite
fi
# TODO: check for open ports instead
port=17822
cp ../ubsan.supp .
if [[ $OSTYPE == 'darwin'* ]]; then
DETECT_LEAKS=0
else
DETECT_LEAKS=1
fi
export UBSAN_OPTIONS=suppressions=./ubsan.supp:log_path=./SAN:print_stacktrace=1:halt_on_errors=0
export ASAN_OPTIONS=log_path=./SAN:print_stacktrace=1:check_initialization_order=1:detect_leaks=$DETECT_LEAKS:halt_on_errors=0
function print_san() {
if test -n "$(find . -maxdepth 1 -name 'SAN.*' -print -quit)"
then
cat ./SAN.*
return 1
fi
return 0
}
./DDNet-Server \
"sv_input_fifo server.fifo;
sv_map coverage;
sv_sqlite_file ddnet-server.sqlite;
sv_port $port" &> server.log || fail server "$?" &
./DDNet \
"cl_input_fifo client1.fifo;
player_name client1;
cl_download_skins 0;
connect localhost:$port" &> client1.log || fail client1 "$?" &
sleep 0.5
./DDNet \
"cl_input_fifo client2.fifo;
player_name client2;
cl_download_skins 0;
connect localhost:$port" &> client2.log || fail client2 "$?" &
fails=0
# give the client time to launch and create the fifo file
# but assume after 3 secs that the client crashed before
# being able to create the file
while [[ ! -p client1.fifo ]]
do
fails="$((fails+1))"
if [ "$arg_verbose" == "1" ]
then
echo "[!] client fifo not found (attempts $fails/3)"
fi
if [ "$fails" -gt "2" ]
then
print_san
echo "[-] Error: client possibly crashed on launch"
exit 1
fi
sleep 1
done
sleep 2
kill_all
wait
sleep 1
ranks="$(sqlite3 ddnet-server.sqlite < <(echo "select * from record_race;"))"
num_ranks="$(echo "$ranks" | wc -l | xargs)"
if [ "$ranks" == "" ]
then
touch fail_ranks.txt
echo "[-] Error: no ranks found in database"
elif [ "$num_ranks" != "1" ]
then
touch fail_ranks.txt
echo "[-] Error: expected 1 rank got $num_ranks"
elif ! echo "$ranks" | grep -q client1
then
touch fail_ranks.txt
echo "[-] Error: expected a rank from client1 instead got:"
echo " $ranks"
fi
if test -n "$(find . -maxdepth 1 -name 'fail_*' -print -quit)"
then
if [ "$arg_verbose" == "1" ]
then
for fail in ./fail_*
do
cat "$fail"
done
fi
print_san
echo "[-] Test failed. See errors above."
exit 1
else
echo "[*] all tests passed"
fi
print_san || exit 1

View file

@ -124,14 +124,17 @@ inline float angle(const vector2_base<float> &a)
template<typename T>
inline vector2_base<T> normalize_pre_length(const vector2_base<T> &v, T len)
{
if(len == 0)
return vector2_base<T>();
return vector2_base<T>(v.x / len, v.y / len);
float l = (float)(1.0f / sqrtf(v.x * v.x + v.y * v.y));
return vector2_base<float>(v.x * l, v.y * l);
}
inline vector2_base<float> normalize(const vector2_base<float> &v)
{
float l = (float)(1.0f / sqrtf(v.x * v.x + v.y * v.y));
float divisor = sqrtf(v.x * v.x + v.y * v.y);
if(divisor == 0.0f)
return vector2_base<float>(0.0f, 0.0f);
float l = (float)(1.0f / divisor);
return vector2_base<float>(v.x * l, v.y * l);
}
@ -273,7 +276,10 @@ inline float length(const vector3_base<float> &a)
inline vector3_base<float> normalize(const vector3_base<float> &v)
{
float l = (float)(1.0f / sqrtf(v.x * v.x + v.y * v.y + v.z * v.z));
float divisor = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z);
if(divisor == 0.0f)
return vector3_base<float>(0.0f, 0.0f, 0.0f);
float l = (float)(1.0f / divisor);
return vector3_base<float>(v.x * l, v.y * l, v.z * l);
}

View file

@ -144,12 +144,14 @@ void CGameWorld::RemoveEntity(CEntity *pEnt)
if(pEnt->m_ObjType == ENTTYPE_CHARACTER)
{
CCharacter *pChar = (CCharacter *)pEnt;
int ID = pChar->GetCID();
if(ID >= 0 && ID < MAX_CLIENTS)
if(CCharacter *pChar = dynamic_cast<CCharacter *>(pEnt))
{
m_apCharacters[ID] = 0;
m_Core.m_apCharacters[ID] = 0;
int ID = pChar->GetCID();
if(ID >= 0 && ID < MAX_CLIENTS)
{
m_apCharacters[ID] = 0;
m_Core.m_apCharacters[ID] = 0;
}
}
}