From fefd62bf0419e802da8845a9f8b37fa6abce5f77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AD=BB=E4=BD=93?= <50087831+xx-shitai-xx@users.noreply.github.com> Date: Tue, 6 Dec 2022 02:35:26 +0100 Subject: [PATCH] [WIP] Feature/unit testing (#52) * Initial Catch2 testing stuff * Add unit testing stage to Jenkins * Update docker alpine version * Fix tests * Fix running tests * Make CTest actually know there are tests available * It was case sensitive on my mac * Messing with unit tests, I think were at 20% now * Fix so testing works for any target platform * Update gs2compiler submodule * Make unit testing more verbose * Don't fail the whole build if test failed + notify discord on test failure * Update gs2lib submodule * Fix unit testing + Add linux x86_64 artifact build + improve release setup * Add cmake to linux x86_64 to be able to run CTest * Change path for testing * Try again * Fix copy path for Testing directory * Fix github release * Fix github release * Fix github release * Fix github release * Fix github release * Fix github release * Fix github release * Fix github release * Fix github release * Fix github release * Fix github release * Fix github release * Publish test results * Revert version Co-authored-by: joey <1166538+xtjoeytx@users.noreply.github.com> --- .dockerignore | 1 - CMakeLists.txt | 13 +- Catch_tests/CMakeLists.txt | 21 ++ Catch_tests/CString/CString_tests.cpp | 246 ++++++++++++++++++++ Catch_tests/TPlayer/TPlayer_tests.cpp | 26 +++ JenkinsEnv.json | 42 +++- Jenkinsfile | 162 ++++++------- cmake/AddTest.cmake | 38 +++ cmake/SubdirList.cmake | 10 + docker/gserver-x86_64-linux-gnu.dockerfile | 34 +++ docker/gserver-x86_64-linux-musl.dockerfile | 7 +- docker/v8-x86_64-linux-gnu.dockerfile | 11 + docker/v8-x86_64-linux-musl.dockerfile | 6 +- server/CMakeLists.txt | 30 ++- server/src/main.cpp | 3 +- 15 files changed, 537 insertions(+), 113 deletions(-) create mode 100644 Catch_tests/CMakeLists.txt create mode 100644 Catch_tests/CString/CString_tests.cpp create mode 100644 Catch_tests/TPlayer/TPlayer_tests.cpp create mode 100644 cmake/AddTest.cmake create mode 100644 cmake/SubdirList.cmake create mode 100644 docker/gserver-x86_64-linux-gnu.dockerfile create mode 100644 docker/v8-x86_64-linux-gnu.dockerfile diff --git a/.dockerignore b/.dockerignore index fbf71ff5..509b9b6d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,4 @@ dependencies/v8 -dependencies/depot_tools build cmake-build-debug/ cmake-build-release/ diff --git a/CMakeLists.txt b/CMakeLists.txt index de0029a7..6ea65613 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ # cmake_minimum_required(VERSION 3.0.0) -project(GS2Emu VERSION 3.0.8 DESCRIPTION "Graal Online v1.411 to v6.037 compatible server" LANGUAGES C CXX) +project(GS2Emu VERSION 3.0.7 DESCRIPTION "Graal Online v1.411 to v6.037 compatible server" LANGUAGES C CXX) set(CMAKE_DEBUG_POSTFIX _d) @@ -164,6 +164,7 @@ configure_file( ) if(APPLE) + set(MAKE_TESTS OFF) # Set variables for generating the Info.plist file set(MACOSX_BUNDLE_BUNDLE_VERSION "${VER_FULL}") set(MACOSX_BUNDLE_EXECUTABLE ${PROJECT_NAME}) @@ -175,6 +176,7 @@ if(APPLE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -std=gnu++20") elseif(WIN32) + set(MAKE_TESTS OFF) # Visual C++ Compiler options if(MSVC) # Suppress secure string function warnings @@ -226,15 +228,14 @@ elseif(WIN32) add_definitions(-D_BSD_SOURCE=1) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++20") + endif() # Prevent Windows.h from clashing with the Standard Template Library so we # can use std::min/std::max (see https://support.microsoft.com/kb/143208) add_definitions(-DNOMINMAX) - -elseif(AROS) -elseif(AMIGA) else() + set(MAKE_TESTS ON) if(STATIC) # SET(CMAKE_EXE_LINKER_FLAGS "-static") endif() @@ -296,4 +297,8 @@ add_subdirectory(${PROJECT_SOURCE_DIR}/dependencies/gs2compiler) add_subdirectory(${PROJECT_SOURCE_DIR}/bin) add_subdirectory(${PROJECT_SOURCE_DIR}/server) + +enable_testing() +add_subdirectory(${PROJECT_SOURCE_DIR}/Catch_tests) + set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME}) diff --git a/Catch_tests/CMakeLists.txt b/Catch_tests/CMakeLists.txt new file mode 100644 index 00000000..a6b3f585 --- /dev/null +++ b/Catch_tests/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.22) + +if(MAKE_TESTS) + include(FetchContent) + include(AddTest) + include(SubdirList) + + FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.0.1 # or a later release + ) + + FetchContent_MakeAvailable(Catch2) + + subdir_list(SUBDIRS ${CMAKE_CURRENT_SOURCE_DIR}) + + foreach(subdir ${SUBDIRS}) + add_test_og(${subdir} ${CMAKE_CURRENT_SOURCE_DIR}) + endforeach() +endif() \ No newline at end of file diff --git a/Catch_tests/CString/CString_tests.cpp b/Catch_tests/CString/CString_tests.cpp new file mode 100644 index 00000000..98f3d81c --- /dev/null +++ b/Catch_tests/CString/CString_tests.cpp @@ -0,0 +1,246 @@ +#define CATCH_CONFIG_MAIN +#include "catch2/catch_all.hpp" +#include + +std::ostream& operator << ( std::ostream& os, CString const& value ) { + os << value.toString(); + return os; +} + +SCENARIO( "CString", "[string]" ) { + GIVEN("A constant CString") { + const CString helloWorldStr = "Hello world"; + + THEN("Check substring") { + REQUIRE(helloWorldStr.subString(0) == helloWorldStr); + REQUIRE(helloWorldStr.subString(0, 5) == "Hello"); + REQUIRE(helloWorldStr.subString(0, 200) == helloWorldStr); + REQUIRE(helloWorldStr.subString(200) == ""); + } + + THEN("check some constants") { + REQUIRE(helloWorldStr.left(5) == "Hello"); + REQUIRE(helloWorldStr.right(5) == "world"); + REQUIRE(helloWorldStr.trim() == helloWorldStr); + } + + WHEN("remove the first word") { + CString res = helloWorldStr.remove(0, 5); + + THEN("check for removal") { + REQUIRE(res == " world"); + + AND_THEN("Trim the result") { + REQUIRE(res.trim() == "world"); + } + } + + THEN("atempt to remove invalid range") { + REQUIRE(helloWorldStr.remove(200) == helloWorldStr); + REQUIRE(helloWorldStr.remove(-1) == helloWorldStr); + REQUIRE(helloWorldStr.remove(-1, 0) == helloWorldStr); + + AND_THEN("Remove entire string") { + REQUIRE(helloWorldStr.remove(0).isEmpty()); + } + } + } + + WHEN("remove the first word") { + CString res = helloWorldStr.remove(0, 5); + + THEN("check for removal") { + REQUIRE(res == " world"); + + AND_THEN("Trim the result") { + REQUIRE(res.trim() == "world"); + } + } + } + + WHEN("escape string") { + CString originalStr = CString("Test string's, hope it works out \\\\, \\, \", \', \"\" - / lol"); + CString stringEscaped = originalStr.escape(); + + THEN("check escaped result") { + printf("Test: %s\n", stringEscaped.toString().c_str()); + REQUIRE(originalStr == stringEscaped.unescape()); + } + } + + WHEN("lowercase string") { + CString lowerCase = helloWorldStr.toLower(); + THEN("the string should be lowercase") { + REQUIRE(lowerCase == "hello world"); + } + } + + WHEN("uppercase string") { + CString upperCase = helloWorldStr.toUpper(); + THEN("the string should be uppercase") { + REQUIRE(upperCase == "HELLO WORLD"); + } + } + } + + GIVEN("An empty CString") { + CString test; + + WHEN("we attempt to trim an empty string, we get an empty string") { + REQUIRE(test.trim().isEmpty()); + } + + WHEN("we attempt to set to empty std string") { + test = std::string(""); + THEN("string is empty") { + REQUIRE(test.isEmpty());; + } + } + + WHEN("we attempt to set to empty cstring") { + CString emptyStr(""); + test = CString(emptyStr); + THEN("string is empty") { + REQUIRE(test.isEmpty());; + } + } + + WHEN("we construct a cstring with another CString 'hello'") { + test = "hello"; + CString tmpStr(test); + + THEN("string == hello") { + REQUIRE(tmpStr == "hello"); + } + } + + WHEN("we construct the cstring with an integer") { + test = CString(int(69)); + THEN("test == 69") { + REQUIRE(test == "69"); + } + } + + WHEN("we construct the cstring with an unsigned integer") { + test = CString((unsigned int)2150069); + THEN("check value of conversion to string") { + REQUIRE(test == "2150069"); + } + } + + WHEN("we construct the cstring with a long") { + test = CString((long)-2107481369); + THEN("check value of conversion to string") { + REQUIRE(test == "-2107481369"); + } + } + + WHEN("we construct the cstring with a unsigned long") { + test = CString((unsigned long)3107481369); + THEN("check value of conversion to string") { + REQUIRE(test == "3107481369"); + } + } + + WHEN("we construct the cstring with a long long") { + test = CString((long long)348384823); + THEN("check value of conversion to string") { + REQUIRE(test == "348384823"); + } + } + + WHEN("we construct the cstring with a long long") { + test = CString((unsigned long long)15000321179321415669u); + THEN("check value of conversion to string") { + REQUIRE(test == "15000321179321415669"); + } + } + + WHEN("we construct the cstring with a float (2-precision only)") { + test = CString((float)1.250521); + THEN("check value of conversion to string") { + REQUIRE(test == "1.25"); + } + } + + WHEN("we construct the cstring with a double") { + test = CString((double)4.269); + THEN("same output as std string") { + REQUIRE(test == std::to_string(4.269)); + } + } + } + GIVEN( "CString" ) { + CString test; + + THEN("the string is empty") { + REQUIRE(test.isEmpty()); + REQUIRE(test.length() == 0); + } + + WHEN("we assign the string to another cstring") { + CString other("hello world"); + test = other; + + THEN( "length should be 11" ) { + REQUIRE(test.length() == 11); + } + + THEN("the strings should be the same") { + REQUIRE(test == other); + } + + THEN("string should not be equal to hello") { + REQUIRE(test != "hello"); + } + } + + WHEN( "operator << (const char*)\"hello world\"" ) { + const char* strHelloWorld = "hello world"; + test << strHelloWorld; + + THEN( "length should be 11" ) { + REQUIRE( test.length() == 11 ); + } + + AND_THEN( "return should be \"hello world\"") { + REQUIRE_THAT( test.text(), Catch::Matchers::Equals("hello world") ); + } + } + + WHEN( "operator << (std::string)\"hello world\"" ) { + std::string strHelloWorld = "hello world"; + test << strHelloWorld; + + THEN( "length should be 11" ) { + REQUIRE( test.length() == 11 ); + } + + AND_THEN( "return should be \"hello world\"") { + REQUIRE_THAT( test.text(), Catch::Matchers::Equals("hello world") ); + } + } + + WHEN( "compared with another CString" ) { + test << "hello world"; + CString test2 = "HeLlO wOrLd"; + + int retVal = test.comparei(test2); + + THEN( "return should be true") { + REQUIRE( retVal == true ); + } + } + + WHEN( "compared with another CString" ) { + test << "hello world"; + CString test2 = "HeLlO wOrZd"; + + int retVal = test.comparei(test2); + + THEN( "return should not be true") { + REQUIRE( retVal != true ); + } + } + } +} \ No newline at end of file diff --git a/Catch_tests/TPlayer/TPlayer_tests.cpp b/Catch_tests/TPlayer/TPlayer_tests.cpp new file mode 100644 index 00000000..ad5353a9 --- /dev/null +++ b/Catch_tests/TPlayer/TPlayer_tests.cpp @@ -0,0 +1,26 @@ +#define CATCH_CONFIG_MAIN +#include "catch2/catch_all.hpp" +#include +#include + +SCENARIO( "TPlayer", "[object]" ) { + + GIVEN( "TPlayer" ) { + int id = 123; + auto* server = new TServer("test"); + auto* socket = new CSocket(); + auto* player = new TPlayer(server, (CSocket*)socket, id); + + WHEN( "getting player id" ) { + THEN( "id should be " << id ) { + REQUIRE( player->getId() == id ); + } + } + + WHEN( "getting player servername" ) { + THEN( "name should be test" ) { + REQUIRE( player->getServerName() == "test" ); + } + } + } +} \ No newline at end of file diff --git a/JenkinsEnv.json b/JenkinsEnv.json index 45960a55..f3069ce7 100644 --- a/JenkinsEnv.json +++ b/JenkinsEnv.json @@ -11,7 +11,8 @@ "Tag": "", "Dockerfile": "docker/gserver-x86_64-linux-musl.dockerfile", "BuildIfSuccessful": "image", - "BuildEnv": "--build-arg NPCSERVER=on" + "BuildEnv": "--build-arg NPCSERVER=on", + "RunTests": false } }, { @@ -25,7 +26,38 @@ "Tag": "-no-npcserver", "Dockerfile": "docker/gserver-x86_64-linux-musl.dockerfile", "BuildIfSuccessful": "image", - "BuildEnv": "--build-arg NPCSERVER=off" + "BuildEnv": "--build-arg NPCSERVER=off", + "RunTests": false + } + }, + { + "Title": "Linux x86_64", + "Description": "", + "Type": "docker", + "OS": "linux", + "Config": { + "DockerRoot": "xtjoeytx", + "DockerImage": "gserver-v2", + "Tag": "-linux-x86_64", + "Dockerfile": "docker/gserver-x86_64-linux-gnu.dockerfile", + "BuildIfSuccessful": "artifact", + "BuildEnv": "--build-arg NPCSERVER=on", + "RunTests": true + } + }, + { + "Title": "Linux x86_64 - No npc-server", + "Description": "", + "Type": "docker", + "OS": "linux", + "Config": { + "DockerRoot": "xtjoeytx", + "DockerImage": "gserver-v2", + "Tag": "-linux-x86_64-no-npcserver", + "Dockerfile": "docker/gserver-x86_64-linux-gnu.dockerfile", + "BuildIfSuccessful": "artifact", + "BuildEnv": "--build-arg NPCSERVER=off", + "RunTests": true } }, { @@ -39,7 +71,8 @@ "Tag": "-win64", "Dockerfile": "docker/gserver-x86_64-w64-mingw.dockerfile", "BuildIfSuccessful": "artifact", - "BuildEnv": "--build-arg NPCSERVER=on" + "BuildEnv": "--build-arg NPCSERVER=on", + "RunTests": false } }, { @@ -53,7 +86,8 @@ "Tag": "-win64-no-npcserver", "Dockerfile": "docker/gserver-x86_64-w64-mingw.dockerfile", "BuildIfSuccessful": "artifact", - "BuildEnv": "--build-arg NPCSERVER=off" + "BuildEnv": "--build-arg NPCSERVER=off", + "RunTests": false } } ] diff --git a/Jenkinsfile b/Jenkinsfile index ace20e6f..4b6ec389 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -39,86 +39,14 @@ def killall_jobs() { echo "Done killing" } -def buildStep(dockerImage, os, defines, DOCKERTAG) { - def split_job_name = env.JOB_NAME.split(/\/{1}/); - def fixed_job_name = split_job_name[1].replace('%2F',' '); - def fixed_os = os.replace(' ','-'); - - try{ - stage("Building on \"${dockerImage}\" for \"${os}\"...") { - properties([pipelineTriggers([githubPush()])]) - def commondir = env.WORKSPACE + '/../' + fixed_job_name + '/' - - def pathInContainer - - def dockerImageRef = docker.image("${dockerImage}"); - dockerImageRef.pull(); - - dockerImageRef.inside("") { - //pathInContainer = steps.sh(script: 'echo $PATH', returnStdout: true).trim() - } - checkout scm; - - def tag = ''; - def extra_ver = ''; - if (env.BRANCH_NAME.equals('master')) { - tag = "latest${DOCKERTAG}"; - } else { - tag = "${env.BRANCH_NAME.replace('/','-')}${DOCKERTAG}"; - extra_ver = "-${tag}"; - } - - dockerImageRef.inside("") { - - if (env.CHANGE_ID) { - echo 'Trying to build pull request' - } - - if (!env.CHANGE_ID) { - // sh "rm -rfv publishing/deploy/*" - // sh "mkdir -p publishing/deploy/gs2emu" - } - - dir("dependencies") { - // sh "BUILDARCH=${arch} ./build-v8-linux" - } - - if ("${os}" == "windows") { - - } else { - sh "rm -rfv build/*" - } - - if ("${os}" == "windows") { - bat "cmake -S. -Bbuild/ ${defines} -DCMAKE_BUILD_TYPE=Release -DVER_EXTRA=\"${extra_ver}\" -DCMAKE_CXX_FLAGS_RELEASE=\"-O3 -ffast-math\" .." - bat "cmake --build build/ --config Release --target package -- -j 8" - } else { - sh "cmake -S. -Bbuild/ ${defines} -DCMAKE_BUILD_TYPE=Release -DVER_EXTRA=\"${extra_ver}\" -DCMAKE_CXX_FLAGS_RELEASE=\"-O3 -ffast-math\" .." - sh "cmake --build build/ --config Release --target package -- -j 8" - //sh "cmake --build . --config Release --target package_source -- -j 8" - } - - dir("build") { - archiveArtifacts artifacts: '*.zip,*.tar.gz,*.tgz' - } - - if ("${os}" == "windows") { - bat "rmdir /s /q build" - } - - discordSend description: "Target: ${os} DockerImage: ${dockerImage} successful!", footer: "", link: env.BUILD_URL, result: currentBuild.currentResult, title: "[${split_job_name[0]}] Build Successful: ${fixed_job_name} #${env.BUILD_NUMBER}", webhookURL: env.GS2EMU_WEBHOOK - } - } - } catch(err) { - currentBuild.result = 'FAILURE' - - discordSend description: "Build Failed: ${fixed_job_name} #${env.BUILD_NUMBER} Target: ${os} DockerImage: ${dockerImage} (<${env.BUILD_URL}|Open>)", footer: "", link: env.BUILD_URL, result: currentBuild.currentResult, title: "[${split_job_name[0]}] Build Failed: ${fixed_job_name} #${env.BUILD_NUMBER}", webhookURL: env.GS2EMU_WEBHOOK - notify('Build failed') - throw err - } -} - -def buildStepDocker(DOCKER_ROOT, DOCKERIMAGE, DOCKERTAG, DOCKERFILE, BUILD_NEXT, BUILDENV) { +def buildStepDocker(config) { + def DOCKER_ROOT = config.DockerRoot; + def DOCKERIMAGE = config.DockerImage; + def DOCKERTAG = config.Tag; + def DOCKERFILE = config.Dockerfile; + def BUILD_NEXT = config.BuildIfSuccessful; + def BUILDENV = config.BuildEnv; + def RUN_TESTS = config.RunTests; def split_job_name = env.JOB_NAME.split(/\/{1}/); def fixed_job_name = split_job_name[1].replace('%2F',' '); @@ -136,6 +64,12 @@ def buildStepDocker(DOCKER_ROOT, DOCKERIMAGE, DOCKERTAG, DOCKERFILE, BUILD_NEXT, PUSH_ARTIFACT = BUILD_NEXT.equals('both'); } + if(env.TAG_NAME) { + sh(returnStdout: true, script: "echo '```' > RELEASE_DESCRIPTION.txt"); + env.RELEASE_DESCRIPTION = sh(returnStdout: true, script: "git tag -l --format='%(contents)' ${env.TAG_NAME} >> RELEASE_DESCRIPTION.txt"); + sh(returnStdout: true, script: "echo '```' >> RELEASE_DESCRIPTION.txt"); + } + if (env.BRANCH_NAME.equals('master')) { tag = "latest${DOCKERTAG}"; } else { @@ -168,6 +102,48 @@ def buildStepDocker(DOCKER_ROOT, DOCKERIMAGE, DOCKERTAG, DOCKERFILE, BUILD_NEXT, } } + if (RUN_TESTS) { + stage("Run tests...") { + customImage.inside("") { + try{ + sh "cd /tmp/gserver/build && ctest -T test --no-compress-output --output-on-failure" + } catch(err) { + currentBuild.result = 'FAILURE' + + discordSend description: "Testing Failed: ${fixed_job_name} #${env.BUILD_NUMBER} DockerImage: ${DOCKERIMAGE} (<${env.BUILD_URL}|Open>)", footer: "", link: env.BUILD_URL, result: currentBuild.currentResult, title: "[${split_job_name[0]}] Build Failed: ${fixed_job_name} #${env.BUILD_NUMBER}", webhookURL: env.GS2EMU_WEBHOOK + notify('Build failed') + } + + sh "mkdir -p ./test && cp -fvr /tmp/gserver/build/Testing ./test/Testing" + dir("./test") { + archiveArtifacts ( + artifacts: 'Testing/**/*.xml', + fingerprint: true + ) + stage("Xunit") { + // Process the CTest xml output with the xUnit plugin + xunit ( + testTimeMargin: '3000', + thresholdMode: 1, + thresholds: [ + skipped(failureThreshold: '0'), + failed(failureThreshold: '0') + ], + tools: [CTest( + pattern: 'Testing/**/*.xml', + deleteOutputFiles: true, + failIfNotNew: false, + skipNoTestFiles: true, + stopProcessingIfError: true + )], + skipPublishingChecks: false + ); + } + } + } + } + } + if (PUSH_ARTIFACT) { stage("Archiving artifacts...") { customImage.inside("") { @@ -196,13 +172,16 @@ def buildStepDocker(DOCKER_ROOT, DOCKERIMAGE, DOCKERTAG, DOCKERFILE, BUILD_NEXT, release_type_tag = 'nightly'; } + + if (!env.TAG_NAME) { + sh(returnStdout: true, script: "echo -e '${release_type_tag} releases' > ../RELEASE_DESCRIPTION.txt"); + } + def files = sh(returnStdout: true, script: 'find . -name "*.zip" -o -name "*.tar.gz"'); files = sh (script: "basename ${files}",returnStdout:true).trim() - echo "${files}" - try { - sh "github-release release --user xtjoeytx --repo GServer-v2 --tag ${release_type_tag} --name \"GS2Emu ${release_type_tag}\" --description \"${release_type_tag} releases\" ${pre_release}" + sh "cat ../RELEASE_DESCRIPTION.txt | github-release release --user xtjoeytx --repo GServer-v2 --tag ${release_type_tag} --name \"GS2Emu ${release_type_tag}\" ${pre_release} --description -" } catch(err) { } @@ -242,20 +221,27 @@ node('master') { def branches = [:] def project = readJSON file: "JenkinsEnv.json"; + if (env.TAG_NAME) { + sh(returnStdout: true, script: "echo '```' > RELEASE_DESCRIPTION.txt"); + env.RELEASE_DESCRIPTION = sh(returnStdout: true, script: "git tag -l --format='%(contents)' ${env.TAG_NAME} >> RELEASE_DESCRIPTION.txt"); + sh(returnStdout: true, script: "echo '```' >> RELEASE_DESCRIPTION.txt"); + } project.builds.each { v -> branches["Build ${v.Title}"] = { node(v.OS) { if ("${v.Type}" == "docker") { - buildStepDocker(v.Config.DockerRoot, v.Config.DockerImage, v.Config.Tag, v.Config.Dockerfile, v.Config.BuildIfSuccessful, v.Config.BuildEnv); - } else { - buildStep(v.Config.DockerImage, v.OS, v.Config.Flags, v.Config.Tag) + buildStepDocker(v.Config); } } } } - sh "rm -rf ./*" - parallel branches; + + if (env.TAG_NAME) { + def DESC = sh(returnStdout: true, script: 'cat RELEASE_DESCRIPTION.txt'); + discordSend description: "${DESC}", customUsername: "OpenGraal", customAvatarUrl: "https://pbs.twimg.com/profile_images/1895028712/13460_106738052711614_100001262603030_51047_4149060_n_400x400.jpg", footer: "OpenGraal Team", link: "https://github.com/xtjoeytx/GServer-v2/releases/tag/${env.TAG_NAME}", result: "SUCCESS", title: "GS2Emu v${env.TAG_NAME}", webhookURL: env.GS2EMU_RELEASE_WEBHOOK; + } + sh "rm -rf ./*" } diff --git a/cmake/AddTest.cmake b/cmake/AddTest.cmake new file mode 100644 index 00000000..3ffbfe0a --- /dev/null +++ b/cmake/AddTest.cmake @@ -0,0 +1,38 @@ +function(add_test_og TARGET_NAME TARGET_PATH) + cmake_minimum_required(VERSION 3.22) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) + + file(GLOB_RECURSE TESTS "${TARGET_PATH}/${TARGET_NAME}/*.cpp") + + add_executable(${TARGET_NAME} ${TESTS}) + target_link_libraries(${TARGET_NAME} PRIVATE ${GSERVER_LIBRARY_NAME} Catch2::Catch2WithMain) + target_link_options(${TARGET_NAME} PRIVATE -static -fstack-protector) + target_link_libraries(${TARGET_NAME} PUBLIC -static-libgcc -static-libstdc++) + + target_include_directories(${TARGET_NAME} PUBLIC ${GS2LIB_INCLUDE_DIRECTORY}) + + target_include_directories(${TARGET_NAME} PUBLIC ${GS2COMPILER_INCLUDE_DIRECTORY}) + + target_include_directories(${TARGET_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/server/include) + target_include_directories(${TARGET_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/server/include/TLevel) + target_include_directories(${TARGET_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/server/include/Scripting) + target_include_directories(${TARGET_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/server/include/Scripting/v8) + target_include_directories(${TARGET_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/server/include/Scripting/interface) + target_include_directories(${TARGET_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/server/include/Misc) + target_include_directories(${TARGET_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/server/include/utilities) + target_include_directories(${TARGET_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/server/include/Animation) + + add_dependencies(${TARGET_NAME} ${GSERVER_LIBRARY_NAME}) + + if(V8NPCSERVER) + include_directories(${V8_INCLUDE_DIR}) + endif() + + list(APPEND CMAKE_MODULE_PATH ${catch2_SOURCE_DIR}/extras) + + include(CTest) + include(Catch) + catch_discover_tests(${TARGET_NAME}) + + message(STATUS "Added test ${TARGET_NAME}") +endfunction() diff --git a/cmake/SubdirList.cmake b/cmake/SubdirList.cmake new file mode 100644 index 00000000..57b62a0a --- /dev/null +++ b/cmake/SubdirList.cmake @@ -0,0 +1,10 @@ +MACRO(subdir_list result curdir) + FILE(GLOB children RELATIVE ${curdir} ${curdir}/*) + SET(dirlist "") + FOREACH(child ${children}) + IF(IS_DIRECTORY ${curdir}/${child}) + LIST(APPEND dirlist ${child}) + ENDIF() + ENDFOREACH() + SET(${result} ${dirlist}) +ENDMACRO() \ No newline at end of file diff --git a/docker/gserver-x86_64-linux-gnu.dockerfile b/docker/gserver-x86_64-linux-gnu.dockerfile new file mode 100644 index 00000000..b5bab178 --- /dev/null +++ b/docker/gserver-x86_64-linux-gnu.dockerfile @@ -0,0 +1,34 @@ +ARG NPCSERVER=on +ARG VER_EXTRA="" + +FROM xtjoeytx/v8:9.1.269.9-gnu as v8 + +# GServer Build Environment +FROM amigadev/crosstools:x86_64-linux AS build-env-npcserver-on +ONBUILD COPY --chown=1001:1001 --from=v8 /tmp/v8 /tmp/v8 + +FROM amigadev/crosstools:x86_64-linux AS build-env-npcserver-off + +FROM build-env-npcserver-${NPCSERVER} AS build-env +ARG NPCSERVER +ARG VER_EXTRA +USER 1001 +COPY --chown=1001:1001 ./ /tmp/gserver + +RUN cd /tmp/gserver \ + && ln -s /tmp/v8 /tmp/gserver/dependencies/v8 \ + && cmake -GNinja -S/tmp/gserver -B/tmp/gserver/build -DCMAKE_BUILD_TYPE=Release -DSTATIC=ON -DV8NPCSERVER=${NPCSERVER} -DVER_EXTRA=${VER_EXTRA} -DWOLFSSL=OFF -DUPNP=OFF -DCMAKE_CXX_FLAGS_RELEASE="-O3 -ffast-math" \ + && cmake --build /tmp/gserver/build --target clean \ + && cmake --build /tmp/gserver/build --target package --parallel $(getconf _NPROCESSORS_ONLN) \ + && chmod 777 -R /tmp/gserver/dist \ + && rm -rf /tmp/gserver/dist/_CPack_Packages + +# GServer Run Environment +FROM alpine:3.14 +ARG CACHE_DATE=2021-07-25 +COPY --from=build-env /tmp/gserver/dist /dist +COPY --from=build-env /tmp/gserver/build /tmp/gserver/build +RUN apk add --update libstdc++ libatomic cmake +USER 1001 +WORKDIR /gserver + diff --git a/docker/gserver-x86_64-linux-musl.dockerfile b/docker/gserver-x86_64-linux-musl.dockerfile index 6da1220d..73127937 100644 --- a/docker/gserver-x86_64-linux-musl.dockerfile +++ b/docker/gserver-x86_64-linux-musl.dockerfile @@ -4,10 +4,10 @@ ARG VER_EXTRA="" FROM xtjoeytx/v8:9.1.269.9 as v8 # GServer Build Environment -FROM alpine:3.14 AS build-env-npcserver-on +FROM alpine:3.16 AS build-env-npcserver-on ONBUILD COPY --chown=1001:1001 --from=v8 /tmp/v8 /tmp/gserver/dependencies/v8 -FROM alpine:3.14 AS build-env-npcserver-off +FROM alpine:3.16 AS build-env-npcserver-off FROM build-env-npcserver-${NPCSERVER} AS build-env ARG NPCSERVER @@ -37,10 +37,9 @@ RUN apk add --update --virtual .gserver-build-dependencies \ USER 1001 # GServer Run Environment -FROM alpine:3.14 +FROM alpine:3.16 ARG CACHE_DATE=2021-07-25 COPY --from=build-env /tmp/gserver/bin /gserver -COPY --from=build-env /tmp/gserver/dist /dist COPY entrypoint.sh /gserver/ RUN apk add --update libstdc++ libatomic WORKDIR /gserver diff --git a/docker/v8-x86_64-linux-gnu.dockerfile b/docker/v8-x86_64-linux-gnu.dockerfile new file mode 100644 index 00000000..3dec981e --- /dev/null +++ b/docker/v8-x86_64-linux-gnu.dockerfile @@ -0,0 +1,11 @@ +# Google V8 Build Environment +FROM amigadev/crosstools:x86_64-linux as v8 +COPY dependencies/build-v8-linux /tmp/ + +RUN cd /tmp/ && \ + git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git /tmp/depot_tools && \ + ln -s /usr/bin/python2.7 /tmp/depot_tools/python && \ + ./build-v8-linux + +FROM alpine:3.16 as v8-final +COPY --from=v8 /tmp/v8 /tmp/v8 diff --git a/docker/v8-x86_64-linux-musl.dockerfile b/docker/v8-x86_64-linux-musl.dockerfile index 83162a84..973c248f 100644 --- a/docker/v8-x86_64-linux-musl.dockerfile +++ b/docker/v8-x86_64-linux-musl.dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.12 as gn-builder +FROM alpine:3.16 as gn-builder ARG GN_COMMIT=82d673acb802cee21534c796a59f8cdf26500f53 RUN apk add --update --virtual .gn-build-dependencies \ alpine-sdk \ @@ -47,7 +47,7 @@ RUN set -x && \ rm -rf /var/lib/apt/lists/* # Google V8 Build Environment -FROM alpine:3.12 as v8 +FROM alpine:3.16 as v8 COPY --from=source /tmp/v8 /tmp/v8 COPY --from=gn-builder /usr/local/bin/gn /tmp/v8/buildtools/linux64/gn RUN \ @@ -95,5 +95,5 @@ RUN \ && rm -rf /tmp/v8/third_party /tmp/v8/test \ && apk del --purge .v8-build-dependencies -FROM alpine:3.12 as v8-final +FROM alpine:3.16 as v8-final COPY --from=v8 /tmp/v8 /tmp/v8 diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index daa8baf5..0752dc68 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -56,6 +56,7 @@ list(APPEND SOURCES ${ANIMATION_SOURCES}) include(bin2h) +set(EXE_HEADERS "") set( HEADERS ${PROJECT_BINARY_DIR}/server/include/IConfig.h @@ -153,6 +154,9 @@ else() set(TARGET_NAME ${PROJECT_NAME_LOWER}) endif() +set(TARGET_NAME_OLD ${TARGET_NAME}) +set(TARGET_NAME ${TARGET_NAME}_lib) + if(UPNP AND NOT MINIUPNPC_FOUND) include_directories(${PROJECT_SOURCE_DIR}/dependencies/miniupnp ${PROJECT_SOURCE_DIR}/dependencies/miniupnp/miniupnpc) endif() @@ -161,7 +165,7 @@ endif() include_directories(${PROJECT_SOURCE_DIR}/dependencies/gs2lib/include) if(APPLE) - add_executable(${TARGET_NAME} ${SOURCES} ${HEADERS}) + add_library(${TARGET_NAME} STATIC ${SOURCES} ${HEADERS}) # Enable ARC (automatic reference counting) for OS X build set_property( @@ -183,7 +187,7 @@ elseif(WIN32) DESTINATION ${PROJECT_BINARY_DIR}/ ) - list(APPEND HEADERS + list(APPEND EXE_HEADERS ${PROJECT_BINARY_DIR}/windresrc.h ${PROJECT_BINARY_DIR}/main.rc ) @@ -194,7 +198,7 @@ elseif(WIN32) " -O coff -i -o ") endif() - add_executable(${TARGET_NAME} ${SOURCES} ${HEADERS}) + add_library(${TARGET_NAME} STATIC ${SOURCES} ${HEADERS}) if(MINGW) if(V8NPCSERVER) @@ -222,11 +226,16 @@ elseif(WIN32) endif() endif() elseif(EMSCRIPTEN) - add_executable(${TARGET_NAME} ${SOURCES} ${HEADERS}) + add_library(${TARGET_NAME} STATIC ${SOURCES} ${HEADERS}) else() - add_executable(${TARGET_NAME} ${SOURCES} ${HEADERS}) + add_library(${TARGET_NAME} STATIC ${SOURCES} ${HEADERS}) endif() +target_compile_definitions(${TARGET_NAME} PRIVATE NOMAIN) + +add_executable(${TARGET_NAME_OLD} src/main.cpp ${EXE_HEADERS}) +target_link_libraries(${TARGET_NAME_OLD} PUBLIC ${TARGET_NAME}) + target_link_libraries(${TARGET_NAME} ${CMAKE_THREAD_LIBS_INIT}) if(WIN32) @@ -259,9 +268,10 @@ endif() if (MINGW) target_link_options(${TARGET_NAME} PRIVATE -static -fstack-protector) target_link_libraries(${TARGET_NAME} -static-libgcc -static-libstdc++) - #target_link_libraries(${TARGET_NAME} mingw_stdthreads) + target_link_options(${TARGET_NAME_OLD} PRIVATE -static -fstack-protector) + target_link_libraries(${TARGET_NAME_OLD} PUBLIC -static-libgcc -static-libstdc++) - install(CODE "set(MY_EXE \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET_NAME}.exe\")") + install(CODE "set(MY_EXE \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET_NAME_OLD}.exe\")") # Transfer the value of ${MY_DEPENDENCY_PATHS} into the install script install(CODE "set(MY_DEPENDENCY_PATHS \"${CMAKE_FIND_DLL_PATH}\")") @@ -350,6 +360,10 @@ if(V8NPCSERVER) add_dependencies(${TARGET_NAME} bootstrap_js_to_h) endif() +set(GSERVER_LIBRARY_NAME + "${TARGET_NAME}" + PARENT_SCOPE) + file(GLOB TEXT "${PROJECT_NAME_LOWER}.wasm" ) @@ -360,5 +374,5 @@ install(FILES ${TEXT} DESTINATION ${INSTALL_DEST}) set(INSTALL_DEST .) -install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_DEST}) +install(TARGETS ${TARGET_NAME_OLD} DESTINATION ${INSTALL_DEST}) diff --git a/server/src/main.cpp b/server/src/main.cpp index 540c80c4..cba954d1 100644 --- a/server/src/main.cpp +++ b/server/src/main.cpp @@ -44,6 +44,7 @@ static void getBasePath(); std::atomic_bool shutdownProgram{ false }; +#ifndef NOMAIN int main(int argc, char* argv[]) { if (parseArgs(argc, argv)) @@ -200,7 +201,7 @@ int main(int argc, char* argv[]) return ERR_SUCCESS; } - +#endif /* Extra-Cool Functions :D */