mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 01:58:19 +00:00
Merge #4392
4392: Add SQL/Score tests r=heinrich5991 a=def- <!-- What is the motivation for the changes of this pull request --> ## Checklist - [ ] Tested the change ingame - [ ] Provided screenshots if it is a visual change - [ ] Tested in combination with possibly related configuration options - [ ] Written a unit test if it works standalone, system.c especially - [ ] Considered possible null pointers and out of bounds array indexing - [ ] Changed no physics that affect existing maps - [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional) Co-authored-by: def <dennis@felsin9.de>
This commit is contained in:
commit
3013466b86
20
.github/workflows/build.yaml
vendored
20
.github/workflows/build.yaml
vendored
|
@ -32,6 +32,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
CFLAGS: -Wdeclaration-after-statement -Werror
|
CFLAGS: -Wdeclaration-after-statement -Werror
|
||||||
CXXFLAGS: -Werror
|
CXXFLAGS: -Werror
|
||||||
|
GTEST_FILTER: -*SQLite*
|
||||||
- os: macOS-latest
|
- os: macOS-latest
|
||||||
cmake-args: -G "Unix Makefiles"
|
cmake-args: -G "Unix Makefiles"
|
||||||
build-args: --parallel
|
build-args: --parallel
|
||||||
|
@ -63,7 +64,17 @@ jobs:
|
||||||
- name: Prepare Linux (fancy)
|
- name: Prepare Linux (fancy)
|
||||||
if: contains(matrix.os, 'ubuntu') && matrix.fancy
|
if: contains(matrix.os, 'ubuntu') && matrix.fancy
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install libmariadbclient-dev libwebsockets-dev -y
|
sudo apt-get install libmariadbclient-dev libwebsockets-dev mariadb-server -y
|
||||||
|
sudo rm -rf /var/lib/mysql/
|
||||||
|
sudo mysql_install_db --user=mysql --datadir=/var/lib/mysql/
|
||||||
|
cd /usr; sudo /usr/bin/mysqld_safe --datadir='/var/lib/mysql/' --no-watch
|
||||||
|
sleep 10
|
||||||
|
sudo mariadb <<EOF
|
||||||
|
CREATE DATABASE ddnet;
|
||||||
|
CREATE USER 'ddnet'@'localhost' IDENTIFIED BY 'thebestpassword';
|
||||||
|
GRANT ALL PRIVILEGES ON ddnet.* TO 'ddnet'@'localhost';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
EOF
|
||||||
|
|
||||||
- name: Prepare macOS
|
- name: Prepare macOS
|
||||||
if: contains(matrix.os, 'macOS')
|
if: contains(matrix.os, 'macOS')
|
||||||
|
@ -82,6 +93,7 @@ jobs:
|
||||||
${{ matrix.cmake-path }}cmake ${{ matrix.cmake-args }} -DCMAKE_BUILD_TYPE=Debug -Werror=dev -DDOWNLOAD_GTEST=ON -DDEV=ON -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG=. ..
|
${{ matrix.cmake-path }}cmake ${{ matrix.cmake-args }} -DCMAKE_BUILD_TYPE=Debug -Werror=dev -DDOWNLOAD_GTEST=ON -DDEV=ON -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG=. ..
|
||||||
${{ matrix.cmake-path }}cmake --build . --config Debug --target everything ${{ matrix.build-args }}
|
${{ matrix.cmake-path }}cmake --build . --config Debug --target everything ${{ matrix.build-args }}
|
||||||
- name: Test debug
|
- name: Test debug
|
||||||
|
env: ${{ matrix.env }}
|
||||||
run: |
|
run: |
|
||||||
cd debug
|
cd debug
|
||||||
${{ matrix.cmake-path }}cmake --build . --config Debug --target run_tests ${{ matrix.build-args }}
|
${{ matrix.cmake-path }}cmake --build . --config Debug --target run_tests ${{ matrix.build-args }}
|
||||||
|
@ -95,6 +107,7 @@ jobs:
|
||||||
${{ matrix.cmake-path }}cmake ${{ matrix.cmake-args }} -DCMAKE_BUILD_TYPE=Release -Werror=dev -DDOWNLOAD_GTEST=ON -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE=. ..
|
${{ matrix.cmake-path }}cmake ${{ matrix.cmake-args }} -DCMAKE_BUILD_TYPE=Release -Werror=dev -DDOWNLOAD_GTEST=ON -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE=. ..
|
||||||
${{ matrix.cmake-path }}cmake --build . --config Release --target everything ${{ matrix.build-args }}
|
${{ matrix.cmake-path }}cmake --build . --config Release --target everything ${{ matrix.build-args }}
|
||||||
- name: Test release
|
- name: Test release
|
||||||
|
env: ${{ matrix.env }}
|
||||||
run: |
|
run: |
|
||||||
cd release
|
cd release
|
||||||
${{ matrix.cmake-path }}cmake --build . --config Release --target run_tests ${{ matrix.build-args }}
|
${{ matrix.cmake-path }}cmake --build . --config Release --target run_tests ${{ matrix.build-args }}
|
||||||
|
@ -120,12 +133,13 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
mkdir fancy
|
mkdir fancy
|
||||||
cd fancy
|
cd fancy
|
||||||
${{ matrix.cmake-path }}cmake ${{ matrix.cmake-args }} -DCMAKE_BUILD_TYPE=RelWithDebInfo -DANTIBOT=ON -DMYSQL=ON -DWEBSOCKETS=ON ..
|
${{ matrix.cmake-path }}cmake ${{ matrix.cmake-args }} -DCMAKE_BUILD_TYPE=RelWithDebInfo -DDOWNLOAD_GTEST=ON -DCMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE=. -DANTIBOT=ON -DTEST_MYSQL=ON -DWEBSOCKETS=ON ..
|
||||||
${{ matrix.cmake-path }}cmake --build . --config RelWithDebInfo --target everything ${{ matrix.build-args }}
|
${{ matrix.cmake-path }}cmake --build . --config RelWithDebInfo --target everything ${{ matrix.build-args }}
|
||||||
- name: Test fancy
|
- name: Test fancy
|
||||||
if: matrix.fancy
|
if: matrix.fancy
|
||||||
|
env: ${{ matrix.env }}
|
||||||
run: |
|
run: |
|
||||||
cd release
|
cd fancy
|
||||||
${{ matrix.cmake-path }}cmake --build . --config RelWithDebInfo --target run_tests ${{ matrix.build-args }}
|
${{ matrix.cmake-path }}cmake --build . --config RelWithDebInfo --target run_tests ${{ matrix.build-args }}
|
||||||
./DDNet-Server shutdown
|
./DDNet-Server shutdown
|
||||||
|
|
||||||
|
|
|
@ -101,6 +101,7 @@ endif()
|
||||||
|
|
||||||
option(WEBSOCKETS "Enable websockets support" OFF)
|
option(WEBSOCKETS "Enable websockets support" OFF)
|
||||||
option(MYSQL "Enable mysql support" OFF)
|
option(MYSQL "Enable mysql support" OFF)
|
||||||
|
option(TEST_MYSQL "Test mysql support in unit tests (also sets -DMYSQL=ON)" OFF)
|
||||||
option(AUTOUPDATE "Enable the autoupdater" OFF)
|
option(AUTOUPDATE "Enable the autoupdater" OFF)
|
||||||
option(INFORM_UPDATE "Inform about available updates" ON)
|
option(INFORM_UPDATE "Inform about available updates" ON)
|
||||||
option(VIDEORECORDER "Enable video recording support via FFmpeg" OFF)
|
option(VIDEORECORDER "Enable video recording support via FFmpeg" OFF)
|
||||||
|
@ -117,6 +118,10 @@ option(DISCORD_DYNAMIC "Enable discovering Discord rich presence libraries at ru
|
||||||
option(PREFER_BUNDLED_LIBS "Prefer bundled libraries over system libraries" ${AUTO_DEPENDENCIES_DEFAULT})
|
option(PREFER_BUNDLED_LIBS "Prefer bundled libraries over system libraries" ${AUTO_DEPENDENCIES_DEFAULT})
|
||||||
option(DEV "Don't generate stuff necessary for packaging" OFF)
|
option(DEV "Don't generate stuff necessary for packaging" OFF)
|
||||||
|
|
||||||
|
if(TEST_MYSQL)
|
||||||
|
set(MYSQL ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Set version if not explicitly set
|
# Set version if not explicitly set
|
||||||
if(NOT VERSION)
|
if(NOT VERSION)
|
||||||
set(VERSION ${PROJECT_VERSION})
|
set(VERSION ${PROJECT_VERSION})
|
||||||
|
@ -2112,6 +2117,8 @@ if(SERVER)
|
||||||
save.h
|
save.h
|
||||||
score.cpp
|
score.cpp
|
||||||
score.h
|
score.h
|
||||||
|
scoreworker.cpp
|
||||||
|
scoreworker.h
|
||||||
teams.cpp
|
teams.cpp
|
||||||
teams.h
|
teams.h
|
||||||
teehistorian.cpp
|
teehistorian.cpp
|
||||||
|
@ -2271,6 +2278,7 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
|
||||||
netaddr.cpp
|
netaddr.cpp
|
||||||
packer.cpp
|
packer.cpp
|
||||||
prng.cpp
|
prng.cpp
|
||||||
|
score.cpp
|
||||||
secure_random.cpp
|
secure_random.cpp
|
||||||
serverbrowser.cpp
|
serverbrowser.cpp
|
||||||
serverinfo.cpp
|
serverinfo.cpp
|
||||||
|
@ -2296,10 +2304,18 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
|
||||||
src/engine/client/serverbrowser_ping_cache.cpp
|
src/engine/client/serverbrowser_ping_cache.cpp
|
||||||
src/engine/client/serverbrowser_ping_cache.h
|
src/engine/client/serverbrowser_ping_cache.h
|
||||||
src/engine/client/sqlite.cpp
|
src/engine/client/sqlite.cpp
|
||||||
|
src/engine/server/databases/connection.cpp
|
||||||
|
src/engine/server/databases/connection.h
|
||||||
|
src/engine/server/databases/sqlite.cpp
|
||||||
|
src/engine/server/databases/mysql.cpp
|
||||||
src/engine/server/name_ban.cpp
|
src/engine/server/name_ban.cpp
|
||||||
src/engine/server/name_ban.h
|
src/engine/server/name_ban.h
|
||||||
|
src/engine/server/sql_string_helpers.cpp
|
||||||
|
src/engine/server/sql_string_helpers.h
|
||||||
src/game/server/teehistorian.cpp
|
src/game/server/teehistorian.cpp
|
||||||
src/game/server/teehistorian.h
|
src/game/server/teehistorian.h
|
||||||
|
src/game/server/scoreworker.cpp
|
||||||
|
src/game/server/scoreworker.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(TARGET_TESTRUNNER testrunner)
|
set(TARGET_TESTRUNNER testrunner)
|
||||||
|
@ -2310,7 +2326,7 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
|
||||||
$<TARGET_OBJECTS:game-shared>
|
$<TARGET_OBJECTS:game-shared>
|
||||||
${DEPS}
|
${DEPS}
|
||||||
)
|
)
|
||||||
target_link_libraries(${TARGET_TESTRUNNER} ${LIBS} ${CURL_LIBRARIES} ${GTEST_LIBRARIES})
|
target_link_libraries(${TARGET_TESTRUNNER} ${LIBS} ${CURL_LIBRARIES} ${MYSQL_LIBRARIES} ${GTEST_LIBRARIES})
|
||||||
target_include_directories(${TARGET_TESTRUNNER} PRIVATE ${CURL_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS})
|
target_include_directories(${TARGET_TESTRUNNER} PRIVATE ${CURL_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS})
|
||||||
|
|
||||||
list(APPEND TARGETS_OWN ${TARGET_TESTRUNNER})
|
list(APPEND TARGETS_OWN ${TARGET_TESTRUNNER})
|
||||||
|
@ -2805,9 +2821,12 @@ foreach(target ${TARGETS_OWN})
|
||||||
target_compile_definitions(${target} PRIVATE CONF_HEADLESS_CLIENT)
|
target_compile_definitions(${target} PRIVATE CONF_HEADLESS_CLIENT)
|
||||||
endif()
|
endif()
|
||||||
if(MYSQL)
|
if(MYSQL)
|
||||||
target_compile_definitions(${target} PRIVATE CONF_SQL)
|
target_compile_definitions(${target} PRIVATE CONF_MYSQL)
|
||||||
target_include_directories(${target} PRIVATE ${MYSQL_INCLUDE_DIRS})
|
target_include_directories(${target} PRIVATE ${MYSQL_INCLUDE_DIRS})
|
||||||
endif()
|
endif()
|
||||||
|
if(TEST_MYSQL)
|
||||||
|
target_compile_definitions(${target} PRIVATE CONF_TEST_MYSQL)
|
||||||
|
endif()
|
||||||
if(AUTOUPDATE AND NOT STEAM)
|
if(AUTOUPDATE AND NOT STEAM)
|
||||||
target_compile_definitions(${target} PRIVATE CONF_AUTOUPDATE)
|
target_compile_definitions(${target} PRIVATE CONF_AUTOUPDATE)
|
||||||
endif()
|
endif()
|
||||||
|
|
10
README.md
10
README.md
|
@ -72,6 +72,16 @@ Whether to enable MySQL/MariaDB support for server. Requires at least MySQL 8.0
|
||||||
|
|
||||||
Note that the bundled MySQL libraries might not work properly on your system. If you run into connection problems with the MySQL server, for example that it connects as root while you chose another user, make sure to install your system libraries for the MySQL client. Make sure that the CMake configuration summary says that it found MySQL libs that were not bundled (no "using bundled libs").
|
Note that the bundled MySQL libraries might not work properly on your system. If you run into connection problems with the MySQL server, for example that it connects as root while you chose another user, make sure to install your system libraries for the MySQL client. Make sure that the CMake configuration summary says that it found MySQL libs that were not bundled (no "using bundled libs").
|
||||||
|
|
||||||
|
* **-DTEST_MYSQL=[ON|OFF]** <br>
|
||||||
|
Whether to test MySQL/MariaDB support in GTest based tests. Note that this requires a running MySQL/MariaDB database on localhost with this setup:
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE DATABASE ddnet;
|
||||||
|
CREATE USER 'ddnet'@'localhost' IDENTIFIED BY 'thebestpassword';
|
||||||
|
GRANT ALL PRIVILEGES ON ddnet.* TO 'ddnet'@'localhost';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
```
|
||||||
|
|
||||||
* **-DAUTOUPDATE=[ON|OFF]** <br>
|
* **-DAUTOUPDATE=[ON|OFF]** <br>
|
||||||
Whether to enable the autoupdater. Packagers may want to disable this for their packages. Default value is ON for Windows and Linux.
|
Whether to enable the autoupdater. Packagers may want to disable this for their packages. Default value is ON for Windows and Linux.
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ void IDbConnection::FormatCreateRace(char *aBuf, unsigned int BufferSize)
|
||||||
" GameID VARCHAR(64), "
|
" GameID VARCHAR(64), "
|
||||||
" DDNet7 BOOL DEFAULT FALSE, "
|
" DDNet7 BOOL DEFAULT FALSE, "
|
||||||
" PRIMARY KEY (Map, Name, Time, Timestamp, Server)"
|
" PRIMARY KEY (Map, Name, Time, Timestamp, Server)"
|
||||||
");",
|
")",
|
||||||
GetPrefix(), BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate());
|
GetPrefix(), BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ void IDbConnection::FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, co
|
||||||
" GameID VARCHAR(64), "
|
" GameID VARCHAR(64), "
|
||||||
" DDNet7 BOOL DEFAULT FALSE, "
|
" DDNet7 BOOL DEFAULT FALSE, "
|
||||||
" PRIMARY KEY (ID, Name)"
|
" PRIMARY KEY (ID, Name)"
|
||||||
");",
|
")",
|
||||||
GetPrefix(), BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate(), pIdType);
|
GetPrefix(), BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate(), pIdType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ void IDbConnection::FormatCreateMaps(char *aBuf, unsigned int BufferSize)
|
||||||
" Stars INT DEFAULT 0, "
|
" Stars INT DEFAULT 0, "
|
||||||
" Timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "
|
" Timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "
|
||||||
" PRIMARY KEY (Map)"
|
" PRIMARY KEY (Map)"
|
||||||
");",
|
")",
|
||||||
GetPrefix(), BinaryCollate(), BinaryCollate(), BinaryCollate());
|
GetPrefix(), BinaryCollate(), BinaryCollate(), BinaryCollate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ void IDbConnection::FormatCreateSaves(char *aBuf, unsigned int BufferSize)
|
||||||
" DDNet7 BOOL DEFAULT FALSE, "
|
" DDNet7 BOOL DEFAULT FALSE, "
|
||||||
" SaveID VARCHAR(36) DEFAULT NULL, "
|
" SaveID VARCHAR(36) DEFAULT NULL, "
|
||||||
" PRIMARY KEY (Map, Code)"
|
" PRIMARY KEY (Map, Code)"
|
||||||
");",
|
")",
|
||||||
GetPrefix(), BinaryCollate(), BinaryCollate(), BinaryCollate());
|
GetPrefix(), BinaryCollate(), BinaryCollate(), BinaryCollate());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +81,6 @@ void IDbConnection::FormatCreatePoints(char *aBuf, unsigned int BufferSize)
|
||||||
" Name VARCHAR(%d) COLLATE %s NOT NULL, "
|
" Name VARCHAR(%d) COLLATE %s NOT NULL, "
|
||||||
" Points INT DEFAULT 0, "
|
" Points INT DEFAULT 0, "
|
||||||
" PRIMARY KEY (Name)"
|
" PRIMARY KEY (Name)"
|
||||||
");",
|
")",
|
||||||
GetPrefix(), MAX_NAME_LENGTH, BinaryCollate());
|
GetPrefix(), MAX_NAME_LENGTH, BinaryCollate());
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#include <base/system.h>
|
#include <base/system.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
class IConsole;
|
class IConsole;
|
||||||
|
|
||||||
// can hold one PreparedStatement with Results
|
// can hold one PreparedStatement with Results
|
||||||
|
@ -37,6 +39,8 @@ public:
|
||||||
virtual const char *Random() const = 0;
|
virtual const char *Random() const = 0;
|
||||||
// Get Median Map Time from l.Map
|
// Get Median Map Time from l.Map
|
||||||
virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const = 0;
|
virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const = 0;
|
||||||
|
virtual const char *False() const = 0;
|
||||||
|
virtual const char *True() const = 0;
|
||||||
|
|
||||||
// tries to allocate the connection from the pool established
|
// tries to allocate the connection from the pool established
|
||||||
//
|
//
|
||||||
|
@ -96,9 +100,9 @@ protected:
|
||||||
int MysqlInit();
|
int MysqlInit();
|
||||||
void MysqlUninit();
|
void MysqlUninit();
|
||||||
|
|
||||||
IDbConnection *CreateSqliteConnection(const char *pFilename, bool Setup);
|
std::unique_ptr<IDbConnection> CreateSqliteConnection(const char *pFilename, bool Setup);
|
||||||
// Returns nullptr if MySQL support is not compiled in.
|
// Returns nullptr if MySQL support is not compiled in.
|
||||||
IDbConnection *CreateMysqlConnection(
|
std::unique_ptr<IDbConnection> CreateMysqlConnection(
|
||||||
const char *pDatabase,
|
const char *pDatabase,
|
||||||
const char *pPrefix,
|
const char *pPrefix,
|
||||||
const char *pUser,
|
const char *pUser,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#include "connection.h"
|
#include "connection.h"
|
||||||
|
|
||||||
#if defined(CONF_SQL)
|
#if defined(CONF_MYSQL)
|
||||||
#include <mysql.h>
|
#include <mysql.h>
|
||||||
|
|
||||||
#include <base/tl/threading.h>
|
#include <base/tl/threading.h>
|
||||||
|
@ -73,6 +73,8 @@ public:
|
||||||
virtual const char *InsertIgnore() const { return "INSERT IGNORE"; };
|
virtual const char *InsertIgnore() const { return "INSERT IGNORE"; };
|
||||||
virtual const char *Random() const { return "RAND()"; };
|
virtual const char *Random() const { return "RAND()"; };
|
||||||
virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const;
|
virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const;
|
||||||
|
virtual const char *False() const { return "FALSE"; }
|
||||||
|
virtual const char *True() const { return "TRUE"; }
|
||||||
|
|
||||||
virtual bool Connect(char *pError, int ErrorSize);
|
virtual bool Connect(char *pError, int ErrorSize);
|
||||||
virtual void Disconnect();
|
virtual void Disconnect();
|
||||||
|
@ -702,7 +704,7 @@ bool CMysqlConnection::AddPoints(const char *pPlayer, int Points, char *pError,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
IDbConnection *CreateMysqlConnection(
|
std::unique_ptr<IDbConnection> CreateMysqlConnection(
|
||||||
const char *pDatabase,
|
const char *pDatabase,
|
||||||
const char *pPrefix,
|
const char *pPrefix,
|
||||||
const char *pUser,
|
const char *pUser,
|
||||||
|
@ -711,7 +713,7 @@ IDbConnection *CreateMysqlConnection(
|
||||||
int Port,
|
int Port,
|
||||||
bool Setup)
|
bool Setup)
|
||||||
{
|
{
|
||||||
return new CMysqlConnection(pDatabase, pPrefix, pUser, pPass, pIp, Port, Setup);
|
return std::unique_ptr<IDbConnection>(new CMysqlConnection(pDatabase, pPrefix, pUser, pPass, pIp, Port, Setup));
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
int MysqlInit()
|
int MysqlInit()
|
||||||
|
@ -721,7 +723,7 @@ int MysqlInit()
|
||||||
void MysqlUninit()
|
void MysqlUninit()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
IDbConnection *CreateMysqlConnection(
|
std::unique_ptr<IDbConnection> CreateMysqlConnection(
|
||||||
const char *pDatabase,
|
const char *pDatabase,
|
||||||
const char *pPrefix,
|
const char *pPrefix,
|
||||||
const char *pUser,
|
const char *pUser,
|
||||||
|
|
|
@ -23,6 +23,11 @@ public:
|
||||||
virtual const char *InsertIgnore() const { return "INSERT OR IGNORE"; };
|
virtual const char *InsertIgnore() const { return "INSERT OR IGNORE"; };
|
||||||
virtual const char *Random() const { return "RANDOM()"; };
|
virtual const char *Random() const { return "RANDOM()"; };
|
||||||
virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const;
|
virtual const char *MedianMapTime(char *pBuffer, int BufferSize) const;
|
||||||
|
// Since SQLite 3.23.0 true/false literals are recognized, but still cleaner to use 1/0, because:
|
||||||
|
// > For compatibility, if there exist columns named "true" or "false", then
|
||||||
|
// > the identifiers refer to the columns rather than Boolean constants.
|
||||||
|
virtual const char *False() const { return "0"; }
|
||||||
|
virtual const char *True() const { return "1"; }
|
||||||
|
|
||||||
virtual bool Connect(char *pError, int ErrorSize);
|
virtual bool Connect(char *pError, int ErrorSize);
|
||||||
virtual void Disconnect();
|
virtual void Disconnect();
|
||||||
|
@ -117,6 +122,11 @@ bool CSqliteConnection::Connect(char *pError, int ErrorSize)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(sqlite3_libversion_number() < 3025000)
|
||||||
|
{
|
||||||
|
dbg_msg("sql", "SQLite version %s is not supported, use at least version 3.25.0", sqlite3_libversion());
|
||||||
|
}
|
||||||
|
|
||||||
int Result = sqlite3_open(m_aFilename, &m_pDb);
|
int Result = sqlite3_open(m_aFilename, &m_pDb);
|
||||||
if(Result != SQLITE_OK)
|
if(Result != SQLITE_OK)
|
||||||
{
|
{
|
||||||
|
@ -357,7 +367,7 @@ bool CSqliteConnection::AddPoints(const char *pPlayer, int Points, char *pError,
|
||||||
str_format(aBuf, sizeof(aBuf),
|
str_format(aBuf, sizeof(aBuf),
|
||||||
"INSERT INTO %s_points(Name, Points) "
|
"INSERT INTO %s_points(Name, Points) "
|
||||||
"VALUES (?, ?) "
|
"VALUES (?, ?) "
|
||||||
"ON CONFLICT(Name) DO UPDATE SET Points=Points+?;",
|
"ON CONFLICT(Name) DO UPDATE SET Points=Points+?",
|
||||||
GetPrefix());
|
GetPrefix());
|
||||||
if(PrepareStatement(aBuf, pError, ErrorSize))
|
if(PrepareStatement(aBuf, pError, ErrorSize))
|
||||||
{
|
{
|
||||||
|
@ -374,7 +384,7 @@ bool CSqliteConnection::AddPoints(const char *pPlayer, int Points, char *pError,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
IDbConnection *CreateSqliteConnection(const char *pFilename, bool Setup)
|
std::unique_ptr<IDbConnection> CreateSqliteConnection(const char *pFilename, bool Setup)
|
||||||
{
|
{
|
||||||
return new CSqliteConnection(pFilename, Setup);
|
return std::unique_ptr<IDbConnection>(new CSqliteConnection(pFilename, Setup));
|
||||||
}
|
}
|
||||||
|
|
|
@ -2419,17 +2419,16 @@ int CServer::Run()
|
||||||
|
|
||||||
if(Config()->m_SvSqliteFile[0] != '\0')
|
if(Config()->m_SvSqliteFile[0] != '\0')
|
||||||
{
|
{
|
||||||
auto pSqlServers = std::unique_ptr<IDbConnection>(CreateSqliteConnection(
|
auto pSqliteConn = CreateSqliteConnection(Config()->m_SvSqliteFile, true);
|
||||||
Config()->m_SvSqliteFile, true));
|
|
||||||
|
|
||||||
if(Config()->m_SvUseSQL)
|
if(Config()->m_SvUseSQL)
|
||||||
{
|
{
|
||||||
DbPool()->RegisterDatabase(std::move(pSqlServers), CDbConnectionPool::WRITE_BACKUP);
|
DbPool()->RegisterDatabase(std::move(pSqliteConn), CDbConnectionPool::WRITE_BACKUP);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto pCopy = std::unique_ptr<IDbConnection>(pSqlServers->Copy());
|
auto pCopy = std::unique_ptr<IDbConnection>(pSqliteConn->Copy());
|
||||||
DbPool()->RegisterDatabase(std::move(pSqlServers), CDbConnectionPool::READ);
|
DbPool()->RegisterDatabase(std::move(pSqliteConn), CDbConnectionPool::READ);
|
||||||
DbPool()->RegisterDatabase(std::move(pCopy), CDbConnectionPool::WRITE);
|
DbPool()->RegisterDatabase(std::move(pCopy), CDbConnectionPool::WRITE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3257,12 +3256,12 @@ void CServer::ConAddSqlServer(IConsole::IResult *pResult, void *pUserData)
|
||||||
|
|
||||||
bool SetUpDb = pResult->NumArguments() == 8 ? pResult->GetInteger(7) : true;
|
bool SetUpDb = pResult->NumArguments() == 8 ? pResult->GetInteger(7) : true;
|
||||||
|
|
||||||
auto pSqlServers = std::unique_ptr<IDbConnection>(CreateMysqlConnection(
|
auto pMysqlConn = CreateMysqlConnection(
|
||||||
pResult->GetString(1), pResult->GetString(2), pResult->GetString(3),
|
pResult->GetString(1), pResult->GetString(2), pResult->GetString(3),
|
||||||
pResult->GetString(4), pResult->GetString(5), pResult->GetInteger(6),
|
pResult->GetString(4), pResult->GetString(5), pResult->GetInteger(6),
|
||||||
SetUpDb));
|
SetUpDb);
|
||||||
|
|
||||||
if(!pSqlServers)
|
if(!pMysqlConn)
|
||||||
{
|
{
|
||||||
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "can't add MySQL server: compiled without MySQL support");
|
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "can't add MySQL server: compiled without MySQL support");
|
||||||
return;
|
return;
|
||||||
|
@ -3275,7 +3274,7 @@ void CServer::ConAddSqlServer(IConsole::IResult *pResult, void *pUserData)
|
||||||
pResult->GetString(1), pResult->GetString(2), pResult->GetString(3),
|
pResult->GetString(1), pResult->GetString(2), pResult->GetString(3),
|
||||||
pResult->GetString(5), pResult->GetInteger(6));
|
pResult->GetString(5), pResult->GetInteger(6));
|
||||||
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
|
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
|
||||||
pSelf->DbPool()->RegisterDatabase(std::move(pSqlServers), ReadOnly ? CDbConnectionPool::READ : CDbConnectionPool::WRITE);
|
pSelf->DbPool()->RegisterDatabase(std::move(pMysqlConn), ReadOnly ? CDbConnectionPool::READ : CDbConnectionPool::WRITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CServer::ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData)
|
void CServer::ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,311 +1,21 @@
|
||||||
#ifndef GAME_SERVER_SCORE_H
|
#ifndef GAME_SERVER_SCORE_H
|
||||||
#define GAME_SERVER_SCORE_H
|
#define GAME_SERVER_SCORE_H
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <engine/map.h>
|
#include <engine/map.h>
|
||||||
#include <engine/server/databases/connection_pool.h>
|
#include <engine/server/databases/connection_pool.h>
|
||||||
#include <game/prng.h>
|
#include <game/prng.h>
|
||||||
#include <game/voting.h>
|
#include <game/voting.h>
|
||||||
|
|
||||||
#include "save.h"
|
#include "save.h"
|
||||||
|
#include "scoreworker.h"
|
||||||
|
|
||||||
struct ISqlData;
|
|
||||||
class IDbConnection;
|
|
||||||
class IServer;
|
|
||||||
class CGameContext;
|
class CGameContext;
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
NUM_CHECKPOINTS = 25,
|
|
||||||
TIMESTAMP_STR_LENGTH = 20, // 2019-04-02 19:38:36
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CScorePlayerResult : ISqlResult
|
|
||||||
{
|
|
||||||
CScorePlayerResult();
|
|
||||||
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
MAX_MESSAGES = 10,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum Variant
|
|
||||||
{
|
|
||||||
DIRECT,
|
|
||||||
ALL,
|
|
||||||
BROADCAST,
|
|
||||||
MAP_VOTE,
|
|
||||||
PLAYER_INFO,
|
|
||||||
} m_MessageKind;
|
|
||||||
union
|
|
||||||
{
|
|
||||||
char m_aaMessages[MAX_MESSAGES][512];
|
|
||||||
char m_aBroadcast[1024];
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
float m_Time;
|
|
||||||
float m_CpTime[NUM_CHECKPOINTS];
|
|
||||||
int m_Score;
|
|
||||||
int m_HasFinishScore;
|
|
||||||
int m_Birthday; // 0 indicates no birthday
|
|
||||||
} m_Info;
|
|
||||||
struct
|
|
||||||
{
|
|
||||||
char m_aReason[VOTE_REASON_LENGTH];
|
|
||||||
char m_aServer[32 + 1];
|
|
||||||
char m_aMap[MAX_MAP_LENGTH + 1];
|
|
||||||
} m_MapVote;
|
|
||||||
} m_Data; // PLAYER_INFO
|
|
||||||
|
|
||||||
void SetVariant(Variant v);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CScoreRandomMapResult : ISqlResult
|
|
||||||
{
|
|
||||||
CScoreRandomMapResult(int ClientID) :
|
|
||||||
m_ClientID(ClientID)
|
|
||||||
{
|
|
||||||
m_aMap[0] = '\0';
|
|
||||||
m_aMessage[0] = '\0';
|
|
||||||
}
|
|
||||||
int m_ClientID;
|
|
||||||
char m_aMap[MAX_MAP_LENGTH];
|
|
||||||
char m_aMessage[512];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CScoreSaveResult : ISqlResult
|
|
||||||
{
|
|
||||||
CScoreSaveResult(int PlayerID, IGameController *Controller) :
|
|
||||||
m_Status(SAVE_FAILED),
|
|
||||||
m_SavedTeam(CSaveTeam(Controller)),
|
|
||||||
m_RequestingPlayer(PlayerID)
|
|
||||||
{
|
|
||||||
m_aMessage[0] = '\0';
|
|
||||||
m_aBroadcast[0] = '\0';
|
|
||||||
}
|
|
||||||
enum
|
|
||||||
{
|
|
||||||
SAVE_SUCCESS,
|
|
||||||
// load team in the following two cases
|
|
||||||
SAVE_FAILED,
|
|
||||||
LOAD_SUCCESS,
|
|
||||||
LOAD_FAILED,
|
|
||||||
} m_Status;
|
|
||||||
char m_aMessage[512];
|
|
||||||
char m_aBroadcast[512];
|
|
||||||
CSaveTeam m_SavedTeam;
|
|
||||||
int m_RequestingPlayer;
|
|
||||||
CUuid m_SaveID;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CScoreInitResult : ISqlResult
|
|
||||||
{
|
|
||||||
CScoreInitResult() :
|
|
||||||
m_CurrentRecord(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
float m_CurrentRecord;
|
|
||||||
};
|
|
||||||
|
|
||||||
class CPlayerData
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
CPlayerData()
|
|
||||||
{
|
|
||||||
Reset();
|
|
||||||
}
|
|
||||||
~CPlayerData() {}
|
|
||||||
|
|
||||||
void Reset()
|
|
||||||
{
|
|
||||||
m_BestTime = 0;
|
|
||||||
m_CurrentTime = 0;
|
|
||||||
for(float &BestCpTime : m_aBestCpTime)
|
|
||||||
BestCpTime = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Set(float Time, float CpTime[NUM_CHECKPOINTS])
|
|
||||||
{
|
|
||||||
m_BestTime = Time;
|
|
||||||
m_CurrentTime = Time;
|
|
||||||
for(int i = 0; i < NUM_CHECKPOINTS; i++)
|
|
||||||
m_aBestCpTime[i] = CpTime[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
float m_BestTime;
|
|
||||||
float m_CurrentTime;
|
|
||||||
float m_aBestCpTime[NUM_CHECKPOINTS];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CSqlInitData : ISqlData
|
|
||||||
{
|
|
||||||
CSqlInitData(std::shared_ptr<CScoreInitResult> pResult) :
|
|
||||||
ISqlData(std::move(pResult))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// current map
|
|
||||||
char m_aMap[MAX_MAP_LENGTH];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CSqlPlayerRequest : ISqlData
|
|
||||||
{
|
|
||||||
CSqlPlayerRequest(std::shared_ptr<CScorePlayerResult> pResult) :
|
|
||||||
ISqlData(std::move(pResult))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// object being requested, either map (128 bytes) or player (16 bytes)
|
|
||||||
char m_aName[MAX_MAP_LENGTH];
|
|
||||||
// current map
|
|
||||||
char m_aMap[MAX_MAP_LENGTH];
|
|
||||||
char m_aRequestingPlayer[MAX_NAME_LENGTH];
|
|
||||||
// relevant for /top5 kind of requests
|
|
||||||
int m_Offset;
|
|
||||||
char m_aServer[5];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CSqlRandomMapRequest : ISqlData
|
|
||||||
{
|
|
||||||
CSqlRandomMapRequest(std::shared_ptr<CScoreRandomMapResult> pResult) :
|
|
||||||
ISqlData(std::move(pResult))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
char m_aServerType[32];
|
|
||||||
char m_aCurrentMap[MAX_MAP_LENGTH];
|
|
||||||
char m_aRequestingPlayer[MAX_NAME_LENGTH];
|
|
||||||
int m_Stars;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CSqlScoreData : ISqlData
|
|
||||||
{
|
|
||||||
CSqlScoreData(std::shared_ptr<CScorePlayerResult> pResult) :
|
|
||||||
ISqlData(std::move(pResult))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~CSqlScoreData(){};
|
|
||||||
|
|
||||||
char m_aMap[MAX_MAP_LENGTH];
|
|
||||||
char m_aGameUuid[UUID_MAXSTRSIZE];
|
|
||||||
char m_aName[MAX_MAP_LENGTH];
|
|
||||||
|
|
||||||
int m_ClientID;
|
|
||||||
float m_Time;
|
|
||||||
char m_aTimestamp[TIMESTAMP_STR_LENGTH];
|
|
||||||
float m_aCpCurrent[NUM_CHECKPOINTS];
|
|
||||||
int m_Num;
|
|
||||||
bool m_Search;
|
|
||||||
char m_aRequestingPlayer[MAX_NAME_LENGTH];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CSqlTeamScoreData : ISqlData
|
|
||||||
{
|
|
||||||
CSqlTeamScoreData() :
|
|
||||||
ISqlData(nullptr)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
char m_aGameUuid[UUID_MAXSTRSIZE];
|
|
||||||
char m_aMap[MAX_MAP_LENGTH];
|
|
||||||
float m_Time;
|
|
||||||
char m_aTimestamp[TIMESTAMP_STR_LENGTH];
|
|
||||||
unsigned int m_Size;
|
|
||||||
char m_aaNames[MAX_CLIENTS][MAX_NAME_LENGTH];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CSqlTeamSave : ISqlData
|
|
||||||
{
|
|
||||||
CSqlTeamSave(std::shared_ptr<CScoreSaveResult> pResult) :
|
|
||||||
ISqlData(std::move(pResult))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
virtual ~CSqlTeamSave(){};
|
|
||||||
|
|
||||||
char m_aClientName[MAX_NAME_LENGTH];
|
|
||||||
char m_aMap[MAX_MAP_LENGTH];
|
|
||||||
char m_aCode[128];
|
|
||||||
char m_aGeneratedCode[128];
|
|
||||||
char m_aServer[5];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CSqlTeamLoad : ISqlData
|
|
||||||
{
|
|
||||||
CSqlTeamLoad(std::shared_ptr<CScoreSaveResult> pResult) :
|
|
||||||
ISqlData(std::move(pResult))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
virtual ~CSqlTeamLoad(){};
|
|
||||||
|
|
||||||
char m_aCode[128];
|
|
||||||
char m_aMap[MAX_MAP_LENGTH];
|
|
||||||
char m_aRequestingPlayer[MAX_NAME_LENGTH];
|
|
||||||
int m_ClientID;
|
|
||||||
// struct holding all player names in the team or an empty string
|
|
||||||
char m_aClientNames[MAX_CLIENTS][MAX_NAME_LENGTH];
|
|
||||||
int m_aClientID[MAX_CLIENTS];
|
|
||||||
int m_NumPlayer;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CTeamrank
|
|
||||||
{
|
|
||||||
CUuid m_TeamID;
|
|
||||||
char m_aaNames[MAX_CLIENTS][MAX_NAME_LENGTH];
|
|
||||||
unsigned int m_NumNames;
|
|
||||||
CTeamrank();
|
|
||||||
|
|
||||||
// Assumes that a database query equivalent to
|
|
||||||
//
|
|
||||||
// SELECT TeamID, Name [, ...] -- the order is important
|
|
||||||
// FROM record_teamrace
|
|
||||||
// ORDER BY TeamID, Name
|
|
||||||
//
|
|
||||||
// was executed and that the result line of the first team member is already selected.
|
|
||||||
// Afterwards the team member of the next team is selected.
|
|
||||||
//
|
|
||||||
// Returns true on SQL failure
|
|
||||||
//
|
|
||||||
// if another team can be extracted
|
|
||||||
bool NextSqlResult(IDbConnection *pSqlServer, bool *pEnd, char *pError, int ErrorSize);
|
|
||||||
|
|
||||||
bool SamePlayers(const std::vector<std::string> *aSortedNames);
|
|
||||||
};
|
|
||||||
|
|
||||||
class CScore
|
class CScore
|
||||||
{
|
{
|
||||||
CPlayerData m_aPlayerData[MAX_CLIENTS];
|
CPlayerData m_aPlayerData[MAX_CLIENTS];
|
||||||
CDbConnectionPool *m_pPool;
|
CDbConnectionPool *m_pPool;
|
||||||
|
|
||||||
static bool Init(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
|
||||||
|
|
||||||
static bool RandomMapThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
|
||||||
static bool RandomUnfinishedMapThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
|
||||||
static bool MapVoteThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
|
||||||
|
|
||||||
static bool LoadPlayerDataThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
|
||||||
static bool MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
|
||||||
static bool ShowRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
|
||||||
static bool ShowTeamRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
|
||||||
static bool ShowTopThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
|
||||||
static bool ShowTeamTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
|
||||||
static bool ShowPlayerTeamTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
|
||||||
static bool ShowTimesThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
|
||||||
static bool ShowPointsThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
|
||||||
static bool ShowTopPointsThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
|
||||||
static bool GetSavesThread(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
|
||||||
|
|
||||||
static bool SaveTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize);
|
|
||||||
static bool LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize);
|
|
||||||
|
|
||||||
static bool SaveScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize);
|
|
||||||
static bool SaveTeamScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize);
|
|
||||||
|
|
||||||
CGameContext *GameServer() const { return m_pGameServer; }
|
CGameContext *GameServer() const { return m_pGameServer; }
|
||||||
IServer *Server() const { return m_pServer; }
|
IServer *Server() const { return m_pServer; }
|
||||||
CGameContext *m_pGameServer;
|
CGameContext *m_pGameServer;
|
||||||
|
|
1660
src/game/server/scoreworker.cpp
Normal file
1660
src/game/server/scoreworker.cpp
Normal file
File diff suppressed because it is too large
Load diff
305
src/game/server/scoreworker.h
Normal file
305
src/game/server/scoreworker.h
Normal file
|
@ -0,0 +1,305 @@
|
||||||
|
#ifndef GAME_SERVER_SCOREWORKER_H
|
||||||
|
#define GAME_SERVER_SCOREWORKER_H
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <engine/map.h>
|
||||||
|
#include <engine/server/databases/connection_pool.h>
|
||||||
|
#include <engine/shared/protocol.h>
|
||||||
|
#include <engine/shared/uuid_manager.h>
|
||||||
|
#include <game/server/save.h>
|
||||||
|
#include <game/voting.h>
|
||||||
|
|
||||||
|
class IDbConnection;
|
||||||
|
class IServer;
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
NUM_CHECKPOINTS = 25,
|
||||||
|
TIMESTAMP_STR_LENGTH = 20, // 2019-04-02 19:38:36
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CScorePlayerResult : ISqlResult
|
||||||
|
{
|
||||||
|
CScorePlayerResult();
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
MAX_MESSAGES = 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Variant
|
||||||
|
{
|
||||||
|
DIRECT,
|
||||||
|
ALL,
|
||||||
|
BROADCAST,
|
||||||
|
MAP_VOTE,
|
||||||
|
PLAYER_INFO,
|
||||||
|
} m_MessageKind;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
char m_aaMessages[MAX_MESSAGES][512];
|
||||||
|
char m_aBroadcast[1024];
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
float m_Time;
|
||||||
|
float m_CpTime[NUM_CHECKPOINTS];
|
||||||
|
int m_Score;
|
||||||
|
int m_HasFinishScore;
|
||||||
|
int m_Birthday; // 0 indicates no birthday
|
||||||
|
} m_Info;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
char m_aReason[VOTE_REASON_LENGTH];
|
||||||
|
char m_aServer[32 + 1];
|
||||||
|
char m_aMap[MAX_MAP_LENGTH + 1];
|
||||||
|
} m_MapVote;
|
||||||
|
} m_Data; // PLAYER_INFO
|
||||||
|
|
||||||
|
void SetVariant(Variant v);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CScoreInitResult : ISqlResult
|
||||||
|
{
|
||||||
|
CScoreInitResult() :
|
||||||
|
m_CurrentRecord(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
float m_CurrentRecord;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CSqlInitData : ISqlData
|
||||||
|
{
|
||||||
|
CSqlInitData(std::shared_ptr<CScoreInitResult> pResult) :
|
||||||
|
ISqlData(std::move(pResult))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// current map
|
||||||
|
char m_aMap[MAX_MAP_LENGTH];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CSqlPlayerRequest : ISqlData
|
||||||
|
{
|
||||||
|
CSqlPlayerRequest(std::shared_ptr<CScorePlayerResult> pResult) :
|
||||||
|
ISqlData(std::move(pResult))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// object being requested, either map (128 bytes) or player (16 bytes)
|
||||||
|
char m_aName[MAX_MAP_LENGTH];
|
||||||
|
// current map
|
||||||
|
char m_aMap[MAX_MAP_LENGTH];
|
||||||
|
char m_aRequestingPlayer[MAX_NAME_LENGTH];
|
||||||
|
// relevant for /top5 kind of requests
|
||||||
|
int m_Offset;
|
||||||
|
char m_aServer[5];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CScoreRandomMapResult : ISqlResult
|
||||||
|
{
|
||||||
|
CScoreRandomMapResult(int ClientID) :
|
||||||
|
m_ClientID(ClientID)
|
||||||
|
{
|
||||||
|
m_aMap[0] = '\0';
|
||||||
|
m_aMessage[0] = '\0';
|
||||||
|
}
|
||||||
|
int m_ClientID;
|
||||||
|
char m_aMap[MAX_MAP_LENGTH];
|
||||||
|
char m_aMessage[512];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CSqlRandomMapRequest : ISqlData
|
||||||
|
{
|
||||||
|
CSqlRandomMapRequest(std::shared_ptr<CScoreRandomMapResult> pResult) :
|
||||||
|
ISqlData(std::move(pResult))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
char m_aServerType[32];
|
||||||
|
char m_aCurrentMap[MAX_MAP_LENGTH];
|
||||||
|
char m_aRequestingPlayer[MAX_NAME_LENGTH];
|
||||||
|
int m_Stars;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CSqlScoreData : ISqlData
|
||||||
|
{
|
||||||
|
CSqlScoreData(std::shared_ptr<CScorePlayerResult> pResult) :
|
||||||
|
ISqlData(std::move(pResult))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~CSqlScoreData(){};
|
||||||
|
|
||||||
|
char m_aMap[MAX_MAP_LENGTH];
|
||||||
|
char m_aGameUuid[UUID_MAXSTRSIZE];
|
||||||
|
char m_aName[MAX_MAP_LENGTH];
|
||||||
|
|
||||||
|
int m_ClientID;
|
||||||
|
float m_Time;
|
||||||
|
char m_aTimestamp[TIMESTAMP_STR_LENGTH];
|
||||||
|
float m_aCpCurrent[NUM_CHECKPOINTS];
|
||||||
|
int m_Num;
|
||||||
|
bool m_Search;
|
||||||
|
char m_aRequestingPlayer[MAX_NAME_LENGTH];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CScoreSaveResult : ISqlResult
|
||||||
|
{
|
||||||
|
CScoreSaveResult(int PlayerID, IGameController *Controller) :
|
||||||
|
m_Status(SAVE_FAILED),
|
||||||
|
m_SavedTeam(CSaveTeam(Controller)),
|
||||||
|
m_RequestingPlayer(PlayerID)
|
||||||
|
{
|
||||||
|
m_aMessage[0] = '\0';
|
||||||
|
m_aBroadcast[0] = '\0';
|
||||||
|
}
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
SAVE_SUCCESS,
|
||||||
|
// load team in the following two cases
|
||||||
|
SAVE_FAILED,
|
||||||
|
LOAD_SUCCESS,
|
||||||
|
LOAD_FAILED,
|
||||||
|
} m_Status;
|
||||||
|
char m_aMessage[512];
|
||||||
|
char m_aBroadcast[512];
|
||||||
|
CSaveTeam m_SavedTeam;
|
||||||
|
int m_RequestingPlayer;
|
||||||
|
CUuid m_SaveID;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CSqlTeamScoreData : ISqlData
|
||||||
|
{
|
||||||
|
CSqlTeamScoreData() :
|
||||||
|
ISqlData(nullptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
char m_aGameUuid[UUID_MAXSTRSIZE];
|
||||||
|
char m_aMap[MAX_MAP_LENGTH];
|
||||||
|
float m_Time;
|
||||||
|
char m_aTimestamp[TIMESTAMP_STR_LENGTH];
|
||||||
|
unsigned int m_Size;
|
||||||
|
char m_aaNames[MAX_CLIENTS][MAX_NAME_LENGTH];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CSqlTeamSave : ISqlData
|
||||||
|
{
|
||||||
|
CSqlTeamSave(std::shared_ptr<CScoreSaveResult> pResult) :
|
||||||
|
ISqlData(std::move(pResult))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual ~CSqlTeamSave(){};
|
||||||
|
|
||||||
|
char m_aClientName[MAX_NAME_LENGTH];
|
||||||
|
char m_aMap[MAX_MAP_LENGTH];
|
||||||
|
char m_aCode[128];
|
||||||
|
char m_aGeneratedCode[128];
|
||||||
|
char m_aServer[5];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CSqlTeamLoad : ISqlData
|
||||||
|
{
|
||||||
|
CSqlTeamLoad(std::shared_ptr<CScoreSaveResult> pResult) :
|
||||||
|
ISqlData(std::move(pResult))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual ~CSqlTeamLoad(){};
|
||||||
|
|
||||||
|
char m_aCode[128];
|
||||||
|
char m_aMap[MAX_MAP_LENGTH];
|
||||||
|
char m_aRequestingPlayer[MAX_NAME_LENGTH];
|
||||||
|
int m_ClientID;
|
||||||
|
// struct holding all player names in the team or an empty string
|
||||||
|
char m_aClientNames[MAX_CLIENTS][MAX_NAME_LENGTH];
|
||||||
|
int m_aClientID[MAX_CLIENTS];
|
||||||
|
int m_NumPlayer;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CPlayerData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CPlayerData()
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
~CPlayerData() {}
|
||||||
|
|
||||||
|
void Reset()
|
||||||
|
{
|
||||||
|
m_BestTime = 0;
|
||||||
|
m_CurrentTime = 0;
|
||||||
|
for(float &BestCpTime : m_aBestCpTime)
|
||||||
|
BestCpTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Set(float Time, float CpTime[NUM_CHECKPOINTS])
|
||||||
|
{
|
||||||
|
m_BestTime = Time;
|
||||||
|
m_CurrentTime = Time;
|
||||||
|
for(int i = 0; i < NUM_CHECKPOINTS; i++)
|
||||||
|
m_aBestCpTime[i] = CpTime[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
float m_BestTime;
|
||||||
|
float m_CurrentTime;
|
||||||
|
float m_aBestCpTime[NUM_CHECKPOINTS];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CTeamrank
|
||||||
|
{
|
||||||
|
CUuid m_TeamID;
|
||||||
|
char m_aaNames[MAX_CLIENTS][MAX_NAME_LENGTH];
|
||||||
|
unsigned int m_NumNames;
|
||||||
|
CTeamrank();
|
||||||
|
|
||||||
|
// Assumes that a database query equivalent to
|
||||||
|
//
|
||||||
|
// SELECT TeamID, Name [, ...] -- the order is important
|
||||||
|
// FROM record_teamrace
|
||||||
|
// ORDER BY TeamID, Name
|
||||||
|
//
|
||||||
|
// was executed and that the result line of the first team member is already selected.
|
||||||
|
// Afterwards the team member of the next team is selected.
|
||||||
|
//
|
||||||
|
// Returns true on SQL failure
|
||||||
|
//
|
||||||
|
// if another team can be extracted
|
||||||
|
bool NextSqlResult(IDbConnection *pSqlServer, bool *pEnd, char *pError, int ErrorSize);
|
||||||
|
|
||||||
|
bool SamePlayers(const std::vector<std::string> *aSortedNames);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CScoreWorker
|
||||||
|
{
|
||||||
|
static bool Init(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
||||||
|
|
||||||
|
static bool RandomMap(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
||||||
|
static bool RandomUnfinishedMap(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
||||||
|
static bool MapVote(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
||||||
|
|
||||||
|
static bool LoadPlayerData(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
||||||
|
static bool MapInfo(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
||||||
|
static bool ShowRank(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
||||||
|
static bool ShowTeamRank(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
||||||
|
static bool ShowTop(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
||||||
|
static bool ShowTeamTop5(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
||||||
|
static bool ShowPlayerTeamTop5(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
||||||
|
static bool ShowTimes(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
||||||
|
static bool ShowPoints(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
||||||
|
static bool ShowTopPoints(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
||||||
|
static bool GetSaves(IDbConnection *pSqlServer, const ISqlData *pGameData, char *pError, int ErrorSize);
|
||||||
|
|
||||||
|
static bool SaveTeam(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize);
|
||||||
|
static bool LoadTeam(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize);
|
||||||
|
|
||||||
|
static bool SaveScore(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize);
|
||||||
|
static bool SaveTeamScore(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure, char *pError, int ErrorSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // GAME_SERVER_SCOREWORKER_H
|
488
src/test/score.cpp
Normal file
488
src/test/score.cpp
Normal file
|
@ -0,0 +1,488 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <base/detect.h>
|
||||||
|
#include <engine/server/databases/connection.h>
|
||||||
|
#include <engine/shared/config.h>
|
||||||
|
#include <game/server/scoreworker.h>
|
||||||
|
|
||||||
|
#include <sqlite3.h>
|
||||||
|
|
||||||
|
#if defined(CONF_TEST_MYSQL)
|
||||||
|
int DummyMysqlInit = (MysqlInit(), 1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
char *CSaveTeam::GetString()
|
||||||
|
{
|
||||||
|
// Dummy implementation for testing
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CSaveTeam::FromString(char const *)
|
||||||
|
{
|
||||||
|
// Dummy implementation for testing
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CSaveTeam::MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientID, int NumPlayer, char *pMessage, int MessageLen)
|
||||||
|
{
|
||||||
|
// Dummy implementation for testing
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(SQLite, Version)
|
||||||
|
{
|
||||||
|
ASSERT_GE(sqlite3_libversion_number(), 3025000) << "SQLite >= 3.25.0 required for Window functions";
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Score : public testing::TestWithParam<IDbConnection *>
|
||||||
|
{
|
||||||
|
Score()
|
||||||
|
{
|
||||||
|
Connect();
|
||||||
|
Init();
|
||||||
|
InsertMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
~Score()
|
||||||
|
{
|
||||||
|
conn->Disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Connect()
|
||||||
|
{
|
||||||
|
ASSERT_FALSE(conn->Connect(aError, sizeof(aError))) << aError;
|
||||||
|
|
||||||
|
// Delete all existing entries for persistent databases like MySQL
|
||||||
|
int NumInserted = 0;
|
||||||
|
ASSERT_FALSE(conn->PrepareStatement("DELETE FROM record_race", aError, sizeof(aError))) << aError;
|
||||||
|
ASSERT_FALSE(conn->ExecuteUpdate(&NumInserted, aError, sizeof(aError))) << aError;
|
||||||
|
ASSERT_FALSE(conn->PrepareStatement("DELETE FROM record_teamrace", aError, sizeof(aError))) << aError;
|
||||||
|
ASSERT_FALSE(conn->ExecuteUpdate(&NumInserted, aError, sizeof(aError))) << aError;
|
||||||
|
ASSERT_FALSE(conn->PrepareStatement("DELETE FROM record_maps", aError, sizeof(aError))) << aError;
|
||||||
|
ASSERT_FALSE(conn->ExecuteUpdate(&NumInserted, aError, sizeof(aError))) << aError;
|
||||||
|
ASSERT_FALSE(conn->PrepareStatement("DELETE FROM record_points", aError, sizeof(aError))) << aError;
|
||||||
|
ASSERT_FALSE(conn->ExecuteUpdate(&NumInserted, aError, sizeof(aError))) << aError;
|
||||||
|
ASSERT_FALSE(conn->PrepareStatement("DELETE FROM record_saves", aError, sizeof(aError))) << aError;
|
||||||
|
ASSERT_FALSE(conn->ExecuteUpdate(&NumInserted, aError, sizeof(aError))) << aError;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Init()
|
||||||
|
{
|
||||||
|
CSqlInitData initData(std::make_shared<CScoreInitResult>());
|
||||||
|
str_copy(initData.m_aMap, "Kobra 3", sizeof(initData.m_aMap));
|
||||||
|
ASSERT_FALSE(CScoreWorker::Init(conn, &initData, aError, sizeof(aError))) << aError;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InsertMap()
|
||||||
|
{
|
||||||
|
char aBuf[512];
|
||||||
|
str_format(aBuf, sizeof(aBuf),
|
||||||
|
"%s into %s_maps(Map, Server, Mapper, Points, Stars, Timestamp) "
|
||||||
|
"VALUES (\"Kobra 3\", \"Novice\", \"Zerodin\", 5, 5, \"2015-01-01 00:00:00\")",
|
||||||
|
conn->InsertIgnore(), conn->GetPrefix());
|
||||||
|
ASSERT_FALSE(conn->PrepareStatement(aBuf, aError, sizeof(aError))) << aError;
|
||||||
|
int NumInserted = 0;
|
||||||
|
ASSERT_FALSE(conn->ExecuteUpdate(&NumInserted, aError, sizeof(aError))) << aError;
|
||||||
|
ASSERT_EQ(NumInserted, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InsertRank()
|
||||||
|
{
|
||||||
|
str_copy(g_Config.m_SvSqlServerName, "USA", sizeof(g_Config.m_SvSqlServerName));
|
||||||
|
CSqlScoreData scoreData(std::make_shared<CScorePlayerResult>());
|
||||||
|
str_copy(scoreData.m_aMap, "Kobra 3", sizeof(scoreData.m_aMap));
|
||||||
|
str_copy(scoreData.m_aGameUuid, "8d300ecf-5873-4297-bee5-95668fdff320", sizeof(scoreData.m_aGameUuid));
|
||||||
|
str_copy(scoreData.m_aName, "nameless tee", sizeof(scoreData.m_aName));
|
||||||
|
scoreData.m_ClientID = 0;
|
||||||
|
scoreData.m_Time = 100.0;
|
||||||
|
str_copy(scoreData.m_aTimestamp, "2021-11-24 19:24:08", sizeof(scoreData.m_aTimestamp));
|
||||||
|
for(int i = 0; i < NUM_CHECKPOINTS; i++)
|
||||||
|
scoreData.m_aCpCurrent[i] = i;
|
||||||
|
str_copy(scoreData.m_aRequestingPlayer, "deen", sizeof(scoreData.m_aRequestingPlayer));
|
||||||
|
ASSERT_FALSE(CScoreWorker::SaveScore(conn, &scoreData, false, aError, sizeof(aError))) << aError;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExpectLines(std::shared_ptr<CScorePlayerResult> pPlayerResult, std::initializer_list<const char *> Lines, bool All = false)
|
||||||
|
{
|
||||||
|
EXPECT_EQ(pPlayerResult->m_MessageKind, All ? CScorePlayerResult::ALL : CScorePlayerResult::DIRECT);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for(const char *pLine : Lines)
|
||||||
|
{
|
||||||
|
EXPECT_STREQ(pPlayerResult->m_Data.m_aaMessages[i], pLine);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(; i < CScorePlayerResult::MAX_MESSAGES; i++)
|
||||||
|
{
|
||||||
|
EXPECT_STREQ(pPlayerResult->m_Data.m_aaMessages[i], "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IDbConnection *conn{GetParam()};
|
||||||
|
char aError[256] = {};
|
||||||
|
std::shared_ptr<CScorePlayerResult> pPlayerResult{std::make_shared<CScorePlayerResult>()};
|
||||||
|
CSqlPlayerRequest playerRequest{pPlayerResult};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SingleScore : public Score
|
||||||
|
{
|
||||||
|
SingleScore()
|
||||||
|
{
|
||||||
|
InsertRank();
|
||||||
|
str_copy(playerRequest.m_aMap, "Kobra 3", sizeof(playerRequest.m_aMap));
|
||||||
|
str_copy(playerRequest.m_aRequestingPlayer, "brainless tee", sizeof(playerRequest.m_aRequestingPlayer));
|
||||||
|
playerRequest.m_Offset = 0;
|
||||||
|
str_copy(playerRequest.m_aServer, "GER", sizeof(playerRequest.m_aServer));
|
||||||
|
str_copy(playerRequest.m_aName, "nameless tee", sizeof(playerRequest.m_aMap));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_P(SingleScore, Top)
|
||||||
|
{
|
||||||
|
ASSERT_FALSE(CScoreWorker::ShowTop(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult,
|
||||||
|
{"------------ Global Top ------------",
|
||||||
|
"1. nameless tee Time: 01:40.00",
|
||||||
|
"------------ GER Top ------------"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(SingleScore, Rank)
|
||||||
|
{
|
||||||
|
ASSERT_FALSE(CScoreWorker::ShowRank(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult, {"nameless tee - 01:40.00 - better than 100% - requested by brainless tee", "Global rank 1 - GER unranked"}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(SingleScore, TopServer)
|
||||||
|
{
|
||||||
|
str_copy(playerRequest.m_aServer, "USA", sizeof(playerRequest.m_aServer));
|
||||||
|
ASSERT_FALSE(CScoreWorker::ShowTop(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult,
|
||||||
|
{"------------ Global Top ------------",
|
||||||
|
"1. nameless tee Time: 01:40.00",
|
||||||
|
"---------------------------------------"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(SingleScore, RankServer)
|
||||||
|
{
|
||||||
|
str_copy(playerRequest.m_aServer, "USA", sizeof(playerRequest.m_aServer));
|
||||||
|
ASSERT_FALSE(CScoreWorker::ShowRank(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult, {"nameless tee - 01:40.00 - better than 100% - requested by brainless tee", "Global rank 1 - USA rank 1"}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(SingleScore, TimesExists)
|
||||||
|
{
|
||||||
|
ASSERT_FALSE(CScoreWorker::ShowTimes(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
EXPECT_EQ(pPlayerResult->m_MessageKind, CScorePlayerResult::DIRECT);
|
||||||
|
EXPECT_STREQ(pPlayerResult->m_Data.m_aaMessages[0], "------------- Last Times -------------");
|
||||||
|
char aBuf[128];
|
||||||
|
str_copy(aBuf, pPlayerResult->m_Data.m_aaMessages[1], 7);
|
||||||
|
EXPECT_STREQ(aBuf, "[USA] ");
|
||||||
|
|
||||||
|
str_copy(aBuf, pPlayerResult->m_Data.m_aaMessages[1] + str_length(pPlayerResult->m_Data.m_aaMessages[1]) - 10, 11);
|
||||||
|
EXPECT_STREQ(aBuf, ", 01:40.00");
|
||||||
|
EXPECT_STREQ(pPlayerResult->m_Data.m_aaMessages[2], "----------------------------------------------------");
|
||||||
|
for(int i = 3; i < CScorePlayerResult::MAX_MESSAGES; i++)
|
||||||
|
{
|
||||||
|
EXPECT_STREQ(pPlayerResult->m_Data.m_aaMessages[i], "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(SingleScore, TimesDoesntExist)
|
||||||
|
{
|
||||||
|
str_copy(playerRequest.m_aName, "foo", sizeof(playerRequest.m_aMap));
|
||||||
|
ASSERT_FALSE(CScoreWorker::ShowTimes(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult, {"There are no times in the specified range"});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TeamScore : public Score
|
||||||
|
{
|
||||||
|
void SetUp()
|
||||||
|
{
|
||||||
|
CSqlTeamScoreData teamScoreData;
|
||||||
|
str_copy(teamScoreData.m_aMap, "Kobra 3", sizeof(teamScoreData.m_aMap));
|
||||||
|
str_copy(teamScoreData.m_aGameUuid, "8d300ecf-5873-4297-bee5-95668fdff320", sizeof(teamScoreData.m_aGameUuid));
|
||||||
|
teamScoreData.m_Size = 2;
|
||||||
|
str_copy(teamScoreData.m_aaNames[0], "nameless tee", sizeof(teamScoreData.m_aaNames[0]));
|
||||||
|
str_copy(teamScoreData.m_aaNames[1], "brainless tee", sizeof(teamScoreData.m_aaNames[1]));
|
||||||
|
teamScoreData.m_Time = 100.0;
|
||||||
|
str_copy(teamScoreData.m_aTimestamp, "2021-11-24 19:24:08", sizeof(teamScoreData.m_aTimestamp));
|
||||||
|
ASSERT_FALSE(CScoreWorker::SaveTeamScore(conn, &teamScoreData, false, aError, sizeof(aError))) << aError;
|
||||||
|
|
||||||
|
str_copy(playerRequest.m_aMap, "Kobra 3", sizeof(playerRequest.m_aMap));
|
||||||
|
str_copy(playerRequest.m_aRequestingPlayer, "brainless tee", sizeof(playerRequest.m_aRequestingPlayer));
|
||||||
|
playerRequest.m_Offset = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_P(TeamScore, All)
|
||||||
|
{
|
||||||
|
ASSERT_FALSE(CScoreWorker::ShowTeamTop5(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult,
|
||||||
|
{"------- Team Top 5 -------",
|
||||||
|
"1. brainless tee & nameless tee Team Time: 01:40.00",
|
||||||
|
"-------------------------------"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(TeamScore, PlayerExists)
|
||||||
|
{
|
||||||
|
str_copy(playerRequest.m_aName, "brainless tee", sizeof(playerRequest.m_aMap));
|
||||||
|
ASSERT_FALSE(CScoreWorker::ShowPlayerTeamTop5(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult,
|
||||||
|
{"------- Team Top 5 -------",
|
||||||
|
"1. brainless tee & nameless tee Team Time: 01:40.00",
|
||||||
|
"-------------------------------"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(TeamScore, PlayerDoesntExist)
|
||||||
|
{
|
||||||
|
str_copy(playerRequest.m_aName, "foo", sizeof(playerRequest.m_aMap));
|
||||||
|
ASSERT_FALSE(CScoreWorker::ShowPlayerTeamTop5(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult, {"foo has no team ranks"});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MapInfo : public Score
|
||||||
|
{
|
||||||
|
MapInfo()
|
||||||
|
{
|
||||||
|
str_copy(playerRequest.m_aRequestingPlayer, "brainless tee", sizeof(playerRequest.m_aRequestingPlayer));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_P(MapInfo, ExactNoFinish)
|
||||||
|
{
|
||||||
|
str_copy(playerRequest.m_aName, "Kobra 3", sizeof(playerRequest.m_aName));
|
||||||
|
ASSERT_FALSE(CScoreWorker::MapInfo(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult, {"\"Kobra 3\" by Zerodin on Novice, ★★★★★, 5 points, released 6 years and 11 months ago, 0 finishes by 0 tees"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(MapInfo, ExactFinish)
|
||||||
|
{
|
||||||
|
InsertRank();
|
||||||
|
str_copy(playerRequest.m_aName, "Kobra 3", sizeof(playerRequest.m_aName));
|
||||||
|
ASSERT_FALSE(CScoreWorker::MapInfo(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult, {"\"Kobra 3\" by Zerodin on Novice, ★★★★★, 5 points, released 6 years and 11 months ago, 1 finish by 1 tee in 01:40 median"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(MapInfo, Fuzzy)
|
||||||
|
{
|
||||||
|
InsertRank();
|
||||||
|
str_copy(playerRequest.m_aName, "k3", sizeof(playerRequest.m_aName));
|
||||||
|
ASSERT_FALSE(CScoreWorker::MapInfo(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult, {"\"Kobra 3\" by Zerodin on Novice, ★★★★★, 5 points, released 6 years and 11 months ago, 1 finish by 1 tee in 01:40 median"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(MapInfo, DoesntExit)
|
||||||
|
{
|
||||||
|
str_copy(playerRequest.m_aName, "f", sizeof(playerRequest.m_aName));
|
||||||
|
ASSERT_FALSE(CScoreWorker::MapInfo(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult, {"No map like \"f\" found."});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MapVote : public Score
|
||||||
|
{
|
||||||
|
MapVote()
|
||||||
|
{
|
||||||
|
str_copy(playerRequest.m_aRequestingPlayer, "brainless tee", sizeof(playerRequest.m_aRequestingPlayer));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_P(MapVote, Exact)
|
||||||
|
{
|
||||||
|
str_copy(playerRequest.m_aName, "Kobra 3", sizeof(playerRequest.m_aName));
|
||||||
|
ASSERT_FALSE(CScoreWorker::MapVote(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
EXPECT_EQ(pPlayerResult->m_MessageKind, CScorePlayerResult::MAP_VOTE);
|
||||||
|
EXPECT_STREQ(pPlayerResult->m_Data.m_MapVote.m_aMap, "Kobra 3");
|
||||||
|
EXPECT_STREQ(pPlayerResult->m_Data.m_MapVote.m_aReason, "/map");
|
||||||
|
EXPECT_STREQ(pPlayerResult->m_Data.m_MapVote.m_aServer, "novice");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(MapVote, Fuzzy)
|
||||||
|
{
|
||||||
|
str_copy(playerRequest.m_aName, "k3", sizeof(playerRequest.m_aName));
|
||||||
|
ASSERT_FALSE(CScoreWorker::MapVote(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
EXPECT_EQ(pPlayerResult->m_MessageKind, CScorePlayerResult::MAP_VOTE);
|
||||||
|
EXPECT_STREQ(pPlayerResult->m_Data.m_MapVote.m_aMap, "Kobra 3");
|
||||||
|
EXPECT_STREQ(pPlayerResult->m_Data.m_MapVote.m_aReason, "/map");
|
||||||
|
EXPECT_STREQ(pPlayerResult->m_Data.m_MapVote.m_aServer, "novice");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(MapVote, DoesntExist)
|
||||||
|
{
|
||||||
|
str_copy(playerRequest.m_aName, "f", sizeof(playerRequest.m_aName));
|
||||||
|
ASSERT_FALSE(CScoreWorker::MapVote(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult, {"No map like \"f\" found. Try adding a '%' at the start if you don't know the first character. Example: /map %castle for \"Out of Castle\""});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Points : public Score
|
||||||
|
{
|
||||||
|
Points()
|
||||||
|
{
|
||||||
|
str_copy(playerRequest.m_aName, "nameless tee", sizeof(playerRequest.m_aName));
|
||||||
|
str_copy(playerRequest.m_aRequestingPlayer, "brainless tee", sizeof(playerRequest.m_aRequestingPlayer));
|
||||||
|
playerRequest.m_Offset = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_P(Points, NoPoints)
|
||||||
|
{
|
||||||
|
ASSERT_FALSE(CScoreWorker::ShowPoints(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult, {"nameless tee has not collected any points so far"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(Points, NoPointsTop)
|
||||||
|
{
|
||||||
|
ASSERT_FALSE(CScoreWorker::ShowTopPoints(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult, {"-------- Top Points --------",
|
||||||
|
"-------------------------------"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(Points, OnePoints)
|
||||||
|
{
|
||||||
|
conn->AddPoints("nameless tee", 2, aError, sizeof(aError));
|
||||||
|
ASSERT_FALSE(CScoreWorker::ShowPoints(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult, {"1. nameless tee Points: 2, requested by brainless tee"}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(Points, OnePointsTop)
|
||||||
|
{
|
||||||
|
conn->AddPoints("nameless tee", 2, aError, sizeof(aError));
|
||||||
|
ASSERT_FALSE(CScoreWorker::ShowTopPoints(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult,
|
||||||
|
{"-------- Top Points --------",
|
||||||
|
"1. nameless tee Points: 2",
|
||||||
|
"-------------------------------"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(Points, TwoPoints)
|
||||||
|
{
|
||||||
|
conn->AddPoints("nameless tee", 2, aError, sizeof(aError));
|
||||||
|
conn->AddPoints("brainless tee", 3, aError, sizeof(aError));
|
||||||
|
ASSERT_FALSE(CScoreWorker::ShowPoints(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult, {"2. nameless tee Points: 2, requested by brainless tee"}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(Points, TwoPointsTop)
|
||||||
|
{
|
||||||
|
conn->AddPoints("nameless tee", 2, aError, sizeof(aError));
|
||||||
|
conn->AddPoints("brainless tee", 3, aError, sizeof(aError));
|
||||||
|
ASSERT_FALSE(CScoreWorker::ShowTopPoints(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult,
|
||||||
|
{"-------- Top Points --------",
|
||||||
|
"1. brainless tee Points: 3",
|
||||||
|
"2. nameless tee Points: 2",
|
||||||
|
"-------------------------------"});
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(Points, EqualPoints)
|
||||||
|
{
|
||||||
|
conn->AddPoints("nameless tee", 2, aError, sizeof(aError));
|
||||||
|
conn->AddPoints("brainless tee", 3, aError, sizeof(aError));
|
||||||
|
conn->AddPoints("nameless tee", 1, aError, sizeof(aError));
|
||||||
|
ASSERT_FALSE(CScoreWorker::ShowPoints(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult, {"1. nameless tee Points: 3, requested by brainless tee"}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(Points, EqualPointsTop)
|
||||||
|
{
|
||||||
|
conn->AddPoints("nameless tee", 2, aError, sizeof(aError));
|
||||||
|
conn->AddPoints("brainless tee", 3, aError, sizeof(aError));
|
||||||
|
conn->AddPoints("nameless tee", 1, aError, sizeof(aError));
|
||||||
|
ASSERT_FALSE(CScoreWorker::ShowTopPoints(conn, &playerRequest, aError, sizeof(aError))) << aError;
|
||||||
|
ExpectLines(pPlayerResult,
|
||||||
|
{"-------- Top Points --------",
|
||||||
|
"1. brainless tee Points: 3",
|
||||||
|
"1. nameless tee Points: 3",
|
||||||
|
"-------------------------------"});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RandomMap : public Score
|
||||||
|
{
|
||||||
|
std::shared_ptr<CScoreRandomMapResult> pRandomMapResult{std::make_shared<CScoreRandomMapResult>(0)};
|
||||||
|
CSqlRandomMapRequest randomMapRequest{pRandomMapResult};
|
||||||
|
|
||||||
|
RandomMap()
|
||||||
|
{
|
||||||
|
str_copy(randomMapRequest.m_aServerType, "Novice", sizeof(randomMapRequest.m_aServerType));
|
||||||
|
str_copy(randomMapRequest.m_aCurrentMap, "Kobra 4", sizeof(randomMapRequest.m_aCurrentMap));
|
||||||
|
str_copy(randomMapRequest.m_aRequestingPlayer, "nameless tee", sizeof(randomMapRequest.m_aRequestingPlayer));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_P(RandomMap, NoStars)
|
||||||
|
{
|
||||||
|
randomMapRequest.m_Stars = -1;
|
||||||
|
ASSERT_FALSE(CScoreWorker::RandomMap(conn, &randomMapRequest, aError, sizeof(aError))) << aError;
|
||||||
|
EXPECT_EQ(pRandomMapResult->m_ClientID, 0);
|
||||||
|
EXPECT_STREQ(pRandomMapResult->m_aMap, "Kobra 3");
|
||||||
|
EXPECT_STREQ(pRandomMapResult->m_aMessage, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(RandomMap, StarsExists)
|
||||||
|
{
|
||||||
|
randomMapRequest.m_Stars = 5;
|
||||||
|
ASSERT_FALSE(CScoreWorker::RandomMap(conn, &randomMapRequest, aError, sizeof(aError))) << aError;
|
||||||
|
EXPECT_EQ(pRandomMapResult->m_ClientID, 0);
|
||||||
|
EXPECT_STREQ(pRandomMapResult->m_aMap, "Kobra 3");
|
||||||
|
EXPECT_STREQ(pRandomMapResult->m_aMessage, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(RandomMap, StarsDoesntExist)
|
||||||
|
{
|
||||||
|
randomMapRequest.m_Stars = 3;
|
||||||
|
ASSERT_FALSE(CScoreWorker::RandomMap(conn, &randomMapRequest, aError, sizeof(aError))) << aError;
|
||||||
|
EXPECT_EQ(pRandomMapResult->m_ClientID, 0);
|
||||||
|
EXPECT_STREQ(pRandomMapResult->m_aMap, "");
|
||||||
|
EXPECT_STREQ(pRandomMapResult->m_aMessage, "No maps found on this server!");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(RandomMap, UnfinishedExists)
|
||||||
|
{
|
||||||
|
randomMapRequest.m_Stars = -1;
|
||||||
|
ASSERT_FALSE(CScoreWorker::RandomUnfinishedMap(conn, &randomMapRequest, aError, sizeof(aError))) << aError;
|
||||||
|
EXPECT_EQ(pRandomMapResult->m_ClientID, 0);
|
||||||
|
EXPECT_STREQ(pRandomMapResult->m_aMap, "Kobra 3");
|
||||||
|
EXPECT_STREQ(pRandomMapResult->m_aMessage, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(RandomMap, UnfinishedDoesntExist)
|
||||||
|
{
|
||||||
|
InsertRank();
|
||||||
|
ASSERT_FALSE(CScoreWorker::RandomUnfinishedMap(conn, &randomMapRequest, aError, sizeof(aError))) << aError;
|
||||||
|
EXPECT_EQ(pRandomMapResult->m_ClientID, 0);
|
||||||
|
EXPECT_STREQ(pRandomMapResult->m_aMap, "");
|
||||||
|
EXPECT_STREQ(pRandomMapResult->m_aMessage, "You have no more unfinished maps on this server!");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pSqliteConn = CreateSqliteConnection(":memory:", true);
|
||||||
|
#if defined(CONF_TEST_MYSQL)
|
||||||
|
auto pMysqlConn = CreateMysqlConnection("ddnet", "record", "ddnet", "thebestpassword", "localhost", 3306, true);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto testValues
|
||||||
|
{
|
||||||
|
testing::Values(pSqliteConn.get()
|
||||||
|
#if defined(CONF_TEST_MYSQL)
|
||||||
|
,
|
||||||
|
pMysqlConn.get()
|
||||||
|
#endif
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
#define INSTANTIATE(SUITE) \
|
||||||
|
INSTANTIATE_TEST_SUITE_P(Sql, SUITE, testValues, \
|
||||||
|
[](const testing::TestParamInfo<Score::ParamType> &info) { \
|
||||||
|
switch(info.index) \
|
||||||
|
{ \
|
||||||
|
case 0: return "SQLite"; \
|
||||||
|
case 1: return "MySQL"; \
|
||||||
|
default: return "Unknown"; \
|
||||||
|
} \
|
||||||
|
})
|
||||||
|
|
||||||
|
INSTANTIATE(SingleScore);
|
||||||
|
INSTANTIATE(TeamScore);
|
||||||
|
INSTANTIATE(MapInfo);
|
||||||
|
INSTANTIATE(MapVote);
|
||||||
|
INSTANTIATE(Points);
|
||||||
|
INSTANTIATE(RandomMap);
|
Loading…
Reference in a new issue