From 05efc25f328ff7c652449f5180b9c8727e51c294 Mon Sep 17 00:00:00 2001 From: ChillerDrgon Date: Mon, 18 Apr 2022 19:28:37 +0200 Subject: [PATCH 1/3] Add integration test script and coverage map --- .github/workflows/clang-sanitizer.yml | 3 + .gitignore | 1 + scripts/integration_test.sh | 216 ++++++++++++++++++++++++++ test/README.md | 6 + test/maps/coverage.map | Bin 0 -> 2148 bytes 5 files changed, 226 insertions(+) create mode 100755 scripts/integration_test.sh create mode 100644 test/README.md create mode 100644 test/maps/coverage.map diff --git a/.github/workflows/clang-sanitizer.yml b/.github/workflows/clang-sanitizer.yml index aeb77f0e4..f1147964b 100644 --- a/.github/workflows/clang-sanitizer.yml +++ b/.github/workflows/clang-sanitizer.yml @@ -43,3 +43,6 @@ jobs: cat ./SAN.* exit 1 fi + - name: Run integration tests with ASan and UBSan + run: | + ./scripts/integration_test.sh san diff --git a/.gitignore b/.gitignore index 9196025a1..980f3c96a 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,7 @@ generated target /build* +/test/* # IDE project files .cache diff --git a/scripts/integration_test.sh b/scripts/integration_test.sh new file mode 100755 index 000000000..90169ad4c --- /dev/null +++ b/scripts/integration_test.sh @@ -0,0 +1,216 @@ +#!/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 test +cp "$arg_build_dir"/DDNet* test + +cd 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 . + +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=1: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;"))" +if [ "$ranks" == "" ] +then + touch fail_ranks.txt + echo "[-] Error: no ranks found in database" +elif [ "$(echo "$ranks" | wc -l)" != "1" ] +then + touch fail_ranks.txt + echo "[-] Error: expected 1 rank got $(echo "$ranks" | wc -l)" +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 + diff --git a/test/README.md b/test/README.md new file mode 100644 index 000000000..e5003243a --- /dev/null +++ b/test/README.md @@ -0,0 +1,6 @@ +# tests + +Use the test script at the root of the repo to run tests + + ./scripts/integration_test.sh + diff --git a/test/maps/coverage.map b/test/maps/coverage.map new file mode 100644 index 0000000000000000000000000000000000000000..1d1e283c8ff90ebef01c6db54994c1960f6bce65 GIT binary patch literal 2148 zcmbtV3rv$&6uy1&E);M|${UBEf|iX1R$kTzs2~p|NN`RKFv@^L1I}ik5M^kB6p+o4 zPzD<;us9;!fF&egVZ<_o0g*A>+$cs6$P6B9SK%fgI{HB2#2>i_f=mGS#o(mrHmNkT!f=7-9 zVxD#!997Q^dABLA2Irt+k_sWN8fp#tSs!AOV5-cs?((;mIK-%Af|Gq^p)huLZV_ z%14iDMJ^fvFo6G_mXGsjnD>?zRDTddgR7wWUC~0WZhh2$-k9supw^|Uk1-fH%Co@_ zPYn0bYsPo=`5LTsYM{oN9<&}`-&(=|}vP^S)Oof<%WX$_n+0)Rec1fII#~Oa zt}vH62OvU783S$@-FZA));5-o3hsFTYnZz!j+^vf!|;VE2v?>CUb@ z?cLp>Asq+e2|3iG9}iPI54!D;b|32OjD;J}^9Qwfb$T>3pIX%}VvK z43-u|iEb2+RBQK}McULdx zvEJEBcIjD-ns|%;^6Pyfo3saus)VCa*B45k4BI^GoJ7Zsm-^hE+mn2(Z=3j0g8kw^ z=$J*CyLxQvH~B$5saXn8Qov*vr zrW{QV`bn8ydgHjVKQ|#bW!r4T_PqI|S-DzvB(eE#wPQqAb4{U3S7W1fhtl=CkX!p4guu{vy48-woYXa&dhc!a`xjZrv3vjk literal 0 HcmV?d00001 From 68bcd21eff9fbace8605eec8122f860499e03c21 Mon Sep 17 00:00:00 2001 From: Dennis Felsing Date: Thu, 26 May 2022 01:22:14 +0200 Subject: [PATCH 2/3] asan+ubsan clean --- scripts/integration_test.sh | 14 ++++++++++---- src/base/vmath.h | 14 ++++++++++---- src/game/client/prediction/gameworld.cpp | 12 +++++++----- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/scripts/integration_test.sh b/scripts/integration_test.sh index 90169ad4c..b9ef6a79f 100755 --- a/scripts/integration_test.sh +++ b/scripts/integration_test.sh @@ -120,8 +120,14 @@ 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=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)" @@ -181,14 +187,15 @@ 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 [ "$(echo "$ranks" | wc -l)" != "1" ] +elif [ "$num_ranks" != "1" ] then touch fail_ranks.txt - echo "[-] Error: expected 1 rank got $(echo "$ranks" | wc -l)" + echo "[-] Error: expected 1 rank got $num_ranks" elif ! echo "$ranks" | grep -q client1 then touch fail_ranks.txt @@ -213,4 +220,3 @@ else fi print_san || exit 1 - diff --git a/src/base/vmath.h b/src/base/vmath.h index 5923fbb9d..7fb074b9a 100644 --- a/src/base/vmath.h +++ b/src/base/vmath.h @@ -124,14 +124,17 @@ inline float angle(const vector2_base &a) template inline vector2_base normalize_pre_length(const vector2_base &v, T len) { + if(len == 0) + return vector2_base(); return vector2_base(v.x / len, v.y / len); - float l = (float)(1.0f / sqrtf(v.x * v.x + v.y * v.y)); - return vector2_base(v.x * l, v.y * l); } inline vector2_base normalize(const vector2_base &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(0.0f, 0.0f); + float l = (float)(1.0f / divisor); return vector2_base(v.x * l, v.y * l); } @@ -273,7 +276,10 @@ inline float length(const vector3_base &a) inline vector3_base normalize(const vector3_base &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(0.0f, 0.0f, 0.0f); + float l = (float)(1.0f / divisor); return vector3_base(v.x * l, v.y * l, v.z * l); } diff --git a/src/game/client/prediction/gameworld.cpp b/src/game/client/prediction/gameworld.cpp index 8d516b5b7..56bf2344f 100644 --- a/src/game/client/prediction/gameworld.cpp +++ b/src/game/client/prediction/gameworld.cpp @@ -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(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; + } } } From e3116217de579bcbca3e108516ae9594d22d6c6a Mon Sep 17 00:00:00 2001 From: Dennis Felsing Date: Thu, 26 May 2022 01:34:18 +0200 Subject: [PATCH 3/3] test -> integration_test --- .gitignore | 2 +- {test => integration_test}/README.md | 0 {test => integration_test}/maps/coverage.map | Bin scripts/integration_test.sh | 6 +++--- 4 files changed, 4 insertions(+), 4 deletions(-) rename {test => integration_test}/README.md (100%) rename {test => integration_test}/maps/coverage.map (100%) diff --git a/.gitignore b/.gitignore index 980f3c96a..22bff0cc3 100644 --- a/.gitignore +++ b/.gitignore @@ -78,7 +78,7 @@ generated target /build* -/test/* +/integration_test/* # IDE project files .cache diff --git a/test/README.md b/integration_test/README.md similarity index 100% rename from test/README.md rename to integration_test/README.md diff --git a/test/maps/coverage.map b/integration_test/maps/coverage.map similarity index 100% rename from test/maps/coverage.map rename to integration_test/maps/coverage.map diff --git a/scripts/integration_test.sh b/scripts/integration_test.sh index b9ef6a79f..a8d570d6b 100755 --- a/scripts/integration_test.sh +++ b/scripts/integration_test.sh @@ -53,10 +53,10 @@ then exit 1 fi -mkdir -p test -cp "$arg_build_dir"/DDNet* test +mkdir -p integration_test +cp "$arg_build_dir"/DDNet* integration_test -cd test || exit 1 +cd integration_test || exit 1 function kill_all() { if [ "$arg_verbose" == "1" ]