diff --git a/.ci/Ubuntu26.04/Dockerfile b/.ci/Ubuntu26.04/Dockerfile index 3166b7eb4..96dd10763 100644 --- a/.ci/Ubuntu26.04/Dockerfile +++ b/.ci/Ubuntu26.04/Dockerfile @@ -9,7 +9,6 @@ RUN apt-get update && \ cmake \ file \ g++ \ - gcovr \ git \ libgl-dev \ liblzma-dev \ diff --git a/.ci/compile.sh b/.ci/compile.sh index 606b9d201..823a31898 100755 --- a/.ci/compile.sh +++ b/.ci/compile.sh @@ -8,17 +8,14 @@ # --suffix renames package with this suffix, requires arg # --server compiles servatrice # --test runs tests -# --sanitize builds with AddressSanitizer and UndefinedBehaviorSanitizer, gcc/clang only -# --coverage instruments the test run for line coverage and prints a gcovr summary, gcc only - # --debug or --release sets the build type ie CMAKE_BUILD_TYPE # --ccache [] uses ccache and shows stats, optionally provide size # --evict-ccache runs ccache eviction based on given age after build # --dir sets the name of the build dir, default is "build" # --cmake-generator sets CMAKE_GENERATOR as used by cmake # --target-macos-version sets the min os version - only used for macOS builds -# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_NO_CLIENT MAKE_TEST USE_CCACHE CCACHE_SIZE CCACHE_EVICTION_AGE BUILD_DIR CMAKE_GENERATOR TARGET_MACOS_VERSION MAKE_SANITIZE MAKE_COVERAGE -# (correspond to args: --debug/--release --install --package --suffix --server --test --sanitize --coverage --ccache --dir ) +# uses env: BUILDTYPE MAKE_INSTALL MAKE_PACKAGE PACKAGE_TYPE PACKAGE_SUFFIX MAKE_SERVER MAKE_NO_CLIENT MAKE_TEST USE_CCACHE CCACHE_SIZE CCACHE_EVICTION_AGE BUILD_DIR CMAKE_GENERATOR TARGET_MACOS_VERSION +# (correspond to args: --debug/--release --install --package --suffix --server --test --ccache --dir ) # exitcode: 1 for failure, 3 for invalid arguments # Read arguments @@ -60,14 +57,6 @@ while [[ $# != 0 ]]; do MAKE_TEST=1 shift ;; - '--sanitize') - MAKE_SANITIZE=1 - shift - ;; - '--coverage') - MAKE_COVERAGE=1 - shift - ;; '--debug') BUILDTYPE="Debug" shift @@ -142,15 +131,6 @@ if [[ ! $BUILD_DIR ]]; then BUILD_DIR="build" fi mkdir -p "$BUILD_DIR" - -if [[ $MAKE_COVERAGE ]]; then - # Resolved while the working directory is still the source root. $0 is relative to the - # directory the script was invoked from, so after the cd below it no longer resolves, and - # under `set -e` the failed subshell would abort the build rather than just yielding an empty - # variable. Only the coverage report needs this, so only coverage pays for it. - SRC_ROOT="$(cd "$(dirname "$0")/.." && pwd)" -fi - cd "$BUILD_DIR" # Set minimum CMake Version @@ -167,17 +147,6 @@ fi if [[ $MAKE_TEST ]]; then flags+=("-DTEST=1") fi -if [[ $MAKE_SANITIZE ]]; then - flags+=("-DENABLE_SANITIZERS=ON") -else - # The release-package step re-runs cmake in the same build dir, so an - # ENABLE_SANITIZERS=ON left over from the debug step would otherwise leak - # into the shipped binaries (libasan/libubsan Depends in the DEB). - flags+=("-DENABLE_SANITIZERS=OFF") -fi -if [[ $MAKE_COVERAGE ]]; then - flags+=("-DENABLE_COVERAGE=ON") -fi if [[ $USE_CCACHE ]]; then flags+=("-DUSE_CCACHE=1") # PCH-aware caching is required or ccache refuses to cache any TU that @@ -337,50 +306,7 @@ fi if [[ $MAKE_TEST ]]; then echo "::group::Run tests" - if [[ $MAKE_SANITIZE ]]; then - # detect_leaks=0: Qt and the sanitizers' own runtimes both leak at exit by design, and - # LeakSanitizer's default is aggressive enough to bury our real findings under hundreds - # of process-teardown reports. Malloc/free misuse and use-after-free are what ASan is for - # and they stay fully on. - # abort_on_error=1: a bad access leaves the process in an unusable state, so continuing - # only produces a second, more confusing report from the wreckage. - # UBSAN halt_on_error=1: without it UBSan prints "runtime error: ..." but the - # process exits 0 (recover mode), so gtest/ctest report a pass and the job - # stays green. Combined with -fno-sanitize-recover=undefined at compile time, - # the first finding aborts the test process with a non-zero exit. - export ASAN_OPTIONS="detect_leaks=0:abort_on_error=1:print_stacktrace=1:symbolize=1" - export UBSAN_OPTIONS="print_stacktrace=1:halt_on_error=1:symbolize=1" - fi - # Every TEST() is its own CTest entry, so the suite is process-spawn bound rather than CPU - # bound and -j recovers nearly all of the per-process overhead. The test binaries are safe to - # run concurrently: the only one that claims a fixed resource is single_instance_manager_test, - # which already scopes its local socket name to its own pid so parallel copies cannot collide. - ctest -C "$BUILDTYPE" --output-on-failure -j "${CTEST_JOBS:-4}" - echo "::endgroup::" -fi - -if [[ $MAKE_COVERAGE ]]; then - # gcovr is not a build dependency and is not available everywhere, so a missing one is reported - # and skipped rather than failing an otherwise green build. - echo "::group::Coverage report" - GCOVR="$(command -v gcovr || true)" - if [[ -z $GCOVR ]]; then - echo "gcovr not found; install it to produce a coverage report" - else - "$GCOVR" --version - # This script configures with `cmake ..`, so the build dir it was started from is the object - # directory and the source root is one level up. Both are passed explicitly: gcovr otherwise - # walks the current directory and trips over in-tree build directories left by other tooling, - # whose stale .gcno files cannot be resolved. --config is named too, so the filters apply no - # matter what directory gcovr happens to start from. - GCOVR_ARGS=("." --root "$SRC_ROOT" --object-directory "." --config "$SRC_ROOT/gcovr.cfg") - "$GCOVR" "${GCOVR_ARGS[@]}" --txt --decisions - "$GCOVR" "${GCOVR_ARGS[@]}" --html-details coverage-details.html --html-title "Cockatrice line coverage" - "$GCOVR" "${GCOVR_ARGS[@]}" --lcov -o coverage.lcov - "$GCOVR" "${GCOVR_ARGS[@]}" --cobertura-pretty -o coverage.xml - "$GCOVR" "${GCOVR_ARGS[@]}" --json-summary-pretty -o coverage-summary.json - echo "Coverage artifacts written to coverage-details.html, coverage.lcov, coverage.xml and coverage-summary.json" - fi + ctest -C "$BUILDTYPE" --output-on-failure echo "::endgroup::" fi diff --git a/.ci/docker.sh b/.ci/docker.sh index 412ad6a1d..46112daaa 100644 --- a/.ci/docker.sh +++ b/.ci/docker.sh @@ -13,17 +13,11 @@ # --set-cache sets the location to cache the image or for ccache # # requires: docker -# uses env: NAME IMAGE_SUFFIX CACHE BUILD GET SAVE INTERACTIVE +# uses env: NAME CACHE BUILD GET SAVE INTERACTIVE # (correspond to args: --set-cache --build --get --save --interactive) # sets env: RUN CCACHE_DIR IMAGE_NAME RUN_ARGS RUN_OPTS BUILD_SCRIPT # exitcode: 1 for failure, 2 for missing dockerfile, 3 for invalid arguments # -# IMAGE_SUFFIX is optional and only appended to the image name, never to NAME, so that a matrix -# leg can have its own image and image cache without needing its own directory under .ci. Two legs -# building and tagging the same image concurrently would race on `docker build --tag`. -# It is deliberately not called SUFFIX: compile.sh already uses SUFFIX for output file names, and -# a job-level SUFFIX would be silently overridden by those per-step env blocks. -# # exported RUN function will run the BUILD_SCRIPT inside of the docker container. # note that the docker container will not inherit any environment variables! # @@ -87,7 +81,7 @@ if ! [[ $NAME ]]; then return 3 fi -export IMAGE_NAME="${project_name,,}_${NAME,,}${IMAGE_SUFFIX:+_${IMAGE_SUFFIX,,}}" # lower case +export IMAGE_NAME="${project_name,,}_${NAME,,}" # lower case docker_dir=".ci/$NAME" if ! [[ -r $docker_dir/Dockerfile ]]; then diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 098aabc4f..17c42b07d 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -144,36 +144,17 @@ jobs: package: DEB - # The one leg that runs the suite under AddressSanitizer and - # UndefinedBehaviorSanitizer. Debian 13 and Fedora 44 keep covering the - # uninstrumented suite, so nothing is lost by instrumenting this one. - sanitize: yes - - - distro: Ubuntu - version: 26.04 - variant: cover # separate image and ccache, same distro - - # The one leg that runs instrumented for line coverage and publishes the report as - # a workflow artifact. It is a separate leg rather than a second flag on the - # sanitizer leg because the two instruments answer different questions: the - # sanitizer leg reports what the run did wrong, the coverage leg reports what the - # run never touched at all. Combining them would also slow both down. Packaging is - # skipped because the leg above already publishes the DEB for this distro. - coverage: yes - package: skip - - name: ${{ matrix.distro }} ${{ matrix.version }}${{ matrix.variant && format(' {0}', matrix.variant) || '' }} + name: ${{ matrix.distro }} ${{ matrix.version }} needs: configure runs-on: ubuntu-latest # https://github.com/actions/runner-images continue-on-error: ${{ matrix.allow-failure == 'yes' }} timeout-minutes: 70 env: - CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }}${{ matrix.variant }} # directory for caching docker image and ccache + CACHE: ${{ github.workspace }}/.cache/${{ matrix.distro }}${{ matrix.version }} # directory for caching docker image and ccache CCACHE_EVICTION_AGE: 7d CCACHE_SIZE: 600M # space of all repo is 10Gi: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy CMAKE_GENERATOR: 'Ninja' NAME: ${{ matrix.distro }}${{ matrix.version }} - IMAGE_SUFFIX: ${{ matrix.variant }} steps: - name: "Checkout" @@ -183,7 +164,7 @@ jobs: id: restore_ccache uses: actions/cache/restore@v6 with: - key: ccache-${{ matrix.distro }}${{ matrix.version }}${{ matrix.variant }} + key: ccache-${{ matrix.distro }}${{ matrix.version }} path: ${{ env.CACHE }} - name: "Build ${{ matrix.distro }} ${{ matrix.version }} Docker image" @@ -193,9 +174,6 @@ jobs: - name: "Build debug and test" if: matrix.test != 'skip' shell: bash - env: - sanitize: '${{ matrix.sanitize }}' - coverage: '${{ matrix.coverage }}' run: | source .ci/docker.sh args=() @@ -203,33 +181,7 @@ jobs: args+=(--ccache "$CCACHE_SIZE") args+=(--cmake-generator "$CMAKE_GENERATOR") - extra=() - [[ $sanitize == yes ]] && extra+=(--sanitize) - [[ $coverage == yes ]] && extra+=(--coverage) - - RUN --server --debug --test "${extra[@]}" "${args[@]}" - - # RUN bind-mounts the workspace into the container, so the report the build wrote into - # build/ is already on the host and no second container is needed to collect it. - - name: "Collect coverage report" - if: matrix.coverage == 'yes' - shell: bash - run: | - mkdir -p coverage - # --html-details writes one page per source file plus a stylesheet and script, and the - # index links to all of them by relative name. Copying the index alone would publish a - # report where every link 404s, so the whole family has to travel together. - cp build/coverage-details* coverage/ - cp build/coverage.lcov build/coverage.xml build/coverage-summary.json coverage/ - ls -la coverage/ - - - name: "Upload coverage artifact" - if: matrix.coverage == 'yes' - uses: actions/upload-artifact@v4 - with: - name: coverage-report - path: coverage/ - if-no-files-found: warn + RUN --server --debug --test "${args[@]}" - name: "Build release package" id: build diff --git a/CMakeLists.txt b/CMakeLists.txt index b272d4ad8..f51c636eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,10 +22,6 @@ option(WITH_ORACLE "Build Cockatrice card database tool (Oracle)" ON) option(WITH_SERVER "Build Cockatrice server (Servatrice)" OFF) # Compile tests option(TEST "Build tests" OFF) -# Build with AddressSanitizer and UndefinedBehaviorSanitizer -option(ENABLE_SANITIZERS "Build with AddressSanitizer and UndefinedBehaviorSanitizer" OFF) -# Build instrumented for GCC line coverage, reported with gcovr -option(ENABLE_COVERAGE "Build instrumented for GCC line coverage" OFF) # Use vcpkg regardless of OS option(USE_VCPKG "Use vcpkg regardless of OS" OFF) @@ -215,12 +211,6 @@ else() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe") endif() -# Always included, because they define cockatrice_disable_sanitizers_for() and -# cockatrice_disable_coverage_for() for the vendored trees. They only touch the global flags -# when ENABLE_SANITIZERS or ENABLE_COVERAGE is on. -include(Sanitizers) -include(Coverage) - # GNU systems need to define the Mersenne exponent for the RNG to compile w/o warning if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") add_definitions("-DSFMT_MEXP=19937") diff --git a/cmake/Coverage.cmake b/cmake/Coverage.cmake deleted file mode 100644 index 0345a2d36..000000000 --- a/cmake/Coverage.cmake +++ /dev/null @@ -1,75 +0,0 @@ -# Line-coverage instrumentation opt-in, plus per-source opt-out -# Input: ENABLE_COVERAGE -# Defines: cockatrice_disable_coverage_for -# -# Included once from the root CMakeLists.txt, immediately after the per-compiler flag -# block, so add_compile_options() reaches every library and executable defined below. -# -# Note the name deliberately shadows CMake's own built-in Modules/Coverage.cmake. It resolves -# to this file only because the root CMakeLists.txt puts cmake/ at the front of -# CMAKE_MODULE_PATH; if that ever moves, include(Coverage) would silently pick up the built-in -# module, which only defines a `COVERAGE` custom target and touches no global flags, so -# instrumentation would quietly stop happening rather than erroring. - -include(CheckCXXSourceCompiles) - -# Opt a set of vendored sources out of instrumentation. -# -# cockatrice_disable_coverage_for( ...) -# -# Third-party code drags the total down with lines no test can ever reach, and a total that -# cannot move is not a useful signal. This is the same carve-out the sanitizer build uses. -# -# The sources must be named relative to the directory that compiles them. CMake resolves a -# source-file property against the directory scope the target was declared in, so calling -# this from the root with absolute paths silently does nothing. That is why the call sites -# live next to the add_library/add_executable that pull the vendored code in. -# -# SKIP_PRECOMPILE_HEADERS mirrors the sanitizer helper. The precompiled header carries the -# instrumentation flags, and mixing an instrumented PCH with an uninstrumented translation -# unit makes GCC reject it. -function(cockatrice_disable_coverage_for) - if(NOT ENABLE_COVERAGE) - return() - endif() - # There is no -fno-coverage: --coverage is shorthand for -fprofile-arcs -ftest-coverage, so both - # halves have to be negated. CC rejects "-fno-coverage" outright, which is how the vendored C - # files below hit it first. - set_source_files_properties( - ${ARGN} PROPERTIES COMPILE_OPTIONS "-fno-profile-arcs;-fno-test-coverage" SKIP_PRECOMPILE_HEADERS ON - ) -endfunction() - -if(ENABLE_COVERAGE) - # Line coverage needs the compiler's own toolchain, so only GCC is supported. Clang's - # equivalent is llvm-cov, which reads a different intermediate format and would need a - # separate report path; rejecting it is better than emitting .gcno files that gcovr cannot - # read. - if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - message(FATAL_ERROR "ENABLE_COVERAGE requires GCC, but the compiler is ${CMAKE_CXX_COMPILER_ID}. " - "Clang coverage needs llvm-cov and is not wired up." - ) - endif() - - # Probe the way the flag will actually be used: compile AND link. A .gcno file comes from the - # compile step, but the runtime that writes .gcda files comes from the link step, so probing - # only the compile step would pass on a toolchain that cannot actually produce coverage. - set(_cockatrice_saved_required_flags "${CMAKE_REQUIRED_FLAGS}") - set(_cockatrice_saved_required_link_options "${CMAKE_REQUIRED_LINK_OPTIONS}") - set(CMAKE_REQUIRED_FLAGS "-O0 -g") - set(CMAKE_REQUIRED_LINK_OPTIONS -fprofile-arcs -ftest-coverage) - check_cxx_source_compiles("int main() { return 0; }" COCKATRICE_HAVE_COVERAGE_FLAGS) - set(CMAKE_REQUIRED_FLAGS "${_cockatrice_saved_required_flags}") - set(CMAKE_REQUIRED_LINK_OPTIONS "${_cockatrice_saved_required_link_options}") - - if(NOT COCKATRICE_HAVE_COVERAGE_FLAGS) - message(FATAL_ERROR "ENABLE_COVERAGE is on, but this toolchain rejected the coverage flags.") - endif() - - # -O0 keeps the line mapping honest: at higher optimisation levels the compiler merges and - # relocates lines, and the report describes code that no longer exists in that form. - add_compile_options(--coverage -O0 -g) - add_link_options(--coverage) - - message(STATUS "Coverage instrumentation enabled (--coverage). Report with gcovr over the build dir.") -endif() diff --git a/cmake/FindQtRuntime.cmake b/cmake/FindQtRuntime.cmake index 2d74aca4d..ad52352de 100644 --- a/cmake/FindQtRuntime.cmake +++ b/cmake/FindQtRuntime.cmake @@ -40,9 +40,7 @@ endif() if(TEST) # Union of Qt modules required across all test targets (independent of application targets). # When adding a new test that needs additional Qt modules, add them here rather than in the test's CMakeLists.txt. - # Test is QtTest: it brings QAbstractItemModelTester and QSignalSpy, which are how the Qt model - # and signal contracts get verified without standing up a GUI. - set(_TEST_NEEDED Concurrent Network Svg Widgets Test) + set(_TEST_NEEDED Concurrent Network Svg Widgets) endif() set(REQUIRED_QT_COMPONENTS ${REQUIRED_QT_COMPONENTS} ${_SERVATRICE_NEEDED} ${_COCKATRICE_NEEDED} ${_ORACLE_NEEDED} diff --git a/cmake/NSIS.template.in b/cmake/NSIS.template.in index b6125238f..4c55c00b3 100644 --- a/cmake/NSIS.template.in +++ b/cmake/NSIS.template.in @@ -304,11 +304,10 @@ SetShellVarContext all ; _?=$INSTDIR keeps the old uninstaller running in place. An NSIS uninstaller ; normally copies itself to %TEMP% and runs from there so that it can delete its ; own directory, and the process we started exits at once. ExecWait then returns -; before the uninstall has run, and the temp copy keeps deleting files - from -; 3.1.0 and 3.1.1 on disk a blanket "RMDir /r $INSTDIR" - while the section -; below is still writing the new payload. _?= disables the copy and makes -; ExecWait block until the uninstall finishes. It must be the last argument -; and must not be quoted, even when the path contains spaces. +; before the uninstall has run, and the temp copy's "RMDir /r $INSTDIR" executes +; while the section below is still writing the new payload. _?= disables the copy +; and makes ExecWait block until the uninstall finishes. It must be the last +; argument and must not be quoted, even when the path contains spaces. ; ; --- 32-bit uninstall --- SetRegView 32 @@ -1108,14 +1107,9 @@ ${AndIf} ${FileExists} "$INSTDIR\portable.dat" ; upgrade portable mode !insertmacro CockLog "section: portable upgrade purge" !insertmacro CockLogListing "before-portable-purge" "*.dll" - ; plugins and translations are payload and are rewritten by the payload - ; below, so removing them here is safe. sounds and themes are payload - ; too - the default packs are rewritten below as well - but a portable - ; user's custom sounds and themes live in exactly those folders, so a - ; recursive purge here would delete user content along with the defaults. - ; Stale default files from an older release are overwritten or replaced - ; by the payload, never by a purge. RMDir /r "$INSTDIR\plugins" + RMDir /r "$INSTDIR\sounds" + RMDir /r "$INSTDIR\themes" RMDir /r "$INSTDIR\translations" Delete "$INSTDIR\uninstall.exe" Delete "$INSTDIR\cockatrice.exe" @@ -1269,32 +1263,16 @@ Section "un.Application" UnSecApplication SetShellVarContext all StrCpy $R1 0 - ; Record whether this was a portable install before portable.dat is - ; removed below. + ; Record whether this was a portable install before the directory (and the + ; portable.dat marker inside it) is removed below. IfFileExists "$INSTDIR\portable.dat" 0 +2 StrCpy $R1 1 - ; Remove only what the installer wrote. The two lists below are generated - ; by CPack from the staged install tree: one Delete per installed file and - ; one non-recursive RMDir per installed directory, children first. - ; Anything the user put in $INSTDIR - decks, pictures, portable data - ; folders - is not on those lists and survives, and $INSTDIR itself is - ; only removed when nothing else is left in it. An earlier version of this - ; section ran a blanket "RMDir /r $INSTDIR" to keep build-tree artifacts - ; out of a fresh reinstall; that also deleted user decks whenever a user - ; had pointed their data folders into the install directory, so payload - ; purity may never be bought at that price again. -@CPACK_NSIS_DELETE_FILES@ -@CPACK_NSIS_DELETE_DIRECTORIES@ - - ; Written by the installer itself, so absent from the staged payload - ; above; qdebug.txt is app-generated log output next to the executables. - Delete "$INSTDIR\uninstall.exe" - Delete "$INSTDIR\portable.dat" - Delete "$INSTDIR\qdebug.txt" - - ; Succeeds only when no foreign files remain. - RMDir "$INSTDIR" + ; Remove the entire application directory so any file that is not part of + ; the installed payload (e.g. build-tree artifacts such as *.dir folders, + ; *_autogen and *.tlog files from a build) cannot survive between an + ; uninstall and a fresh reinstall. + RMDir /r "$INSTDIR" RMDir /r "$SMPROGRAMS\Cockatrice" diff --git a/cmake/Sanitizers.cmake b/cmake/Sanitizers.cmake deleted file mode 100644 index 2e5d794de..000000000 --- a/cmake/Sanitizers.cmake +++ /dev/null @@ -1,109 +0,0 @@ -# AddressSanitizer + UndefinedBehaviorSanitizer opt-in, plus per-source opt-out -# Input: ENABLE_SANITIZERS -# Defines: cockatrice_disable_sanitizers_for -# -# Included once from the root CMakeLists.txt, immediately after the per-compiler flag -# block, so add_compile_options() reaches every library and executable defined below. - -include(CheckCXXSourceCompiles) - -# Opt a set of vendored sources out of instrumentation. -# -# cockatrice_disable_sanitizers_for( ...) -# -# Sanitizer findings inside vendored code are not actionable for us, and letting them -# through buries our own bugs in noise until everyone learns to skip the job. -# -# The sources must be named relative to the directory that compiles them. CMake resolves a -# source-file property against the directory scope the target was declared in, so calling -# this from the root with absolute paths silently does nothing. That is why the call sites -# live next to the add_library/add_executable that pull the vendored code in. -# -# SKIP_PRECOMPILE_HEADERS is not optional. The precompiled header is built with the -# sanitizer flags, so it defines __SANITIZE_ADDRESS__; a translation unit compiled with -# -fno-sanitize=all does not, and GCC then refuses the PCH with -# "not used because __SANITIZE_ADDRESS__ not defined", which is a fatal -Werror=invalid-pch -# in the servatrice and oracle builds. -function(cockatrice_disable_sanitizers_for) - if(NOT ENABLE_SANITIZERS) - return() - endif() - set_source_files_properties(${ARGN} PROPERTIES COMPILE_OPTIONS "-fno-sanitize=all" SKIP_PRECOMPILE_HEADERS ON) -endfunction() - -if(ENABLE_SANITIZERS) - # Probe a flag the way it will actually be used: on the compile step AND the link step. - # check_cxx_compiler_flag() only probes the compile step, which makes every sanitizer - # flag look unsupported because linking an ASan object without -fsanitize=address leaves - # __asan_init undefined. That is a silent misconfiguration rather than an error, so the - # probe has to link. - function(cockatrice_probe_flag FLAG VARNAME) - set(CMAKE_REQUIRED_FLAGS "${FLAG}") - set(CMAKE_REQUIRED_LINK_OPTIONS "${FLAG}") - check_cxx_source_compiles("int main() { return 0; }" ${VARNAME}) - unset(CMAKE_REQUIRED_FLAGS) - unset(CMAKE_REQUIRED_LINK_OPTIONS) - endfunction() - - set(SANITIZER_FLAGS - -fsanitize=address,undefined - -fno-omit-frame-pointer - # UBSan defaults to recover mode: it prints "runtime error: ..." but the - # process exits 0, so gtest/ctest report a pass and CI stays green. - # -fno-sanitize-recover makes the first finding abort instead. This is - # probed like the other cosmetic flags, but CI also sets - # UBSAN_OPTIONS=halt_on_error=1 as the runtime-side guarantee. - -fno-sanitize-recover=undefined - # Qt builds QObject subclasses into raw storage and writes the vptr afterwards, which - # the vptr check reports on essentially every QObject instantiation. The check has no - # value for us because nothing outside moc-generated code inherits QObject, so it is - # off outright rather than suppressed piecemeal. - -fno-sanitize=vptr - ) - - # MSVC has no ASan/UBSan equivalent, and clang-cl would need /fsanitize=address plus a - # matching runtime link, which is a separate piece of work. - if(MSVC) - message(FATAL_ERROR "ENABLE_SANITIZERS is not supported with MSVC") - endif() - - # Probe rather than assume, so an older toolchain gets the sanitizers it can support - # instead of failing the build on a flag it does not recognize. - set(SANITIZER_SUPPORTED_FLAGS) - foreach(FLAG ${SANITIZER_FLAGS}) - string(MAKE_C_IDENTIFIER "COCKATRICE_HAS${FLAG}" _FLAG_VAR) - cockatrice_probe_flag("${FLAG}" ${_FLAG_VAR}) - if(${_FLAG_VAR}) - list(APPEND SANITIZER_SUPPORTED_FLAGS ${FLAG}) - else() - message(STATUS "Sanitizers: ${FLAG} unsupported by ${CMAKE_CXX_COMPILER_ID}, skipping") - endif() - endforeach() - - # The whole point of the option is the sanitizers, so an unsupported pair is fatal even - # though the cosmetic flags above are allowed to degrade. - if(NOT "-fsanitize=address,undefined" IN_LIST SANITIZER_SUPPORTED_FLAGS) - message(FATAL_ERROR "ENABLE_SANITIZERS requires -fsanitize=address,undefined, which " - "${CMAKE_CXX_COMPILER_ID} does not accept" - ) - endif() - - add_compile_options(${SANITIZER_SUPPORTED_FLAGS}) - add_link_options(-fsanitize=address,undefined) - - # Recorded so the test tree can label its entries and a plain `make` at the same time - # cannot quietly disagree with what CI ran. - set(COCKATRICE_SANITIZERS - ON - CACHE INTERNAL "AddressSanitizer and UBSan are enabled" - ) -else() - # ENABLE_SANITIZERS is OFF, but COCKATRICE_SANITIZERS is CACHE INTERNAL, so a - # reused build dir (e.g. CI's release-package step reconfiguring the same dir - # after the sanitized debug step) would otherwise keep skipping NO_SANITIZE - # tests and keep the 300s discovery timeout. Reset it explicitly. - set(COCKATRICE_SANITIZERS - OFF - CACHE INTERNAL "AddressSanitizer and UBSan are enabled" FORCE - ) -endif() diff --git a/cmake/pch/qtcore_pch.h b/cmake/pch/qtcore_pch.h index 176f2939c..cc3dd12ee 100644 --- a/cmake/pch/qtcore_pch.h +++ b/cmake/pch/qtcore_pch.h @@ -3,53 +3,22 @@ * * Safe for every target that links Qt Core, including the headless * Servatrice binary. Keep this header free of any widget/gui types. - * - * Candidates are chosen from IWYU include statistics: a header earns a - * spot when it is not already reachable from the rest of this list and - * enough translation units re-parse it. Qt headers are stable across - * builds, so the precompiled header rarely invalidates. */ #include #include -#include -#include -#include #include #include -#include -#include #include -#include #include -#include -#include -#include -#include -#include #include #include #include -#include #include -#include -#include #include -#include -#include #include -#include -#include -#include -#include #include -#include #include #include -#include -#include #include -#include #include -#include -#include diff --git a/cmake/pch/qtwidgets_pch.h b/cmake/pch/qtwidgets_pch.h index 030bec69c..2c63f450e 100644 --- a/cmake/pch/qtwidgets_pch.h +++ b/cmake/pch/qtwidgets_pch.h @@ -4,56 +4,27 @@ * Includes the Qt Core precompiled header plus the heavy Gui, Widgets and * Network layers that virtually every client translation unit re-parses. * Do not use on Servatrice (headless, QT_DONT_USE_QTGUI). - * - * Candidates are chosen from IWYU include statistics: a header earns a - * spot when it is not already reachable from the rest of this list and - * enough translation units re-parse it. Qt headers are stable across - * builds, so the precompiled header rarely invalidates. */ #include "qtcore_pch.h" #include #include -#include -#include -#include -#include -#include -#include -#include #include #include #include -#include #include -#include -#include -#include -#include #include -#include -#include #include #include -#include #include #include -#include #include #include #include -#include #include #include -#include -#include -#include #include -#include -#include #include -#include #include -#include #include diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 282f5a3c6..396ef7c34 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -59,7 +59,6 @@ set(cockatrice_SOURCES src/interface/widgets/dialogs/dlg_update.cpp src/interface/widgets/dialogs/dlg_view_log.cpp src/interface/widgets/dialogs/override_printing_warning.cpp - src/interface/widgets/dialogs/path_safety_warning.cpp src/interface/widgets/dialogs/settings_view.cpp src/interface/widgets/dialogs/tip_of_the_day.cpp src/interface/widgets/deck_share/deck_share_utils.cpp diff --git a/cockatrice/src/game/game_event_handler.cpp b/cockatrice/src/game/game_event_handler.cpp index 7b08af47c..b86f661bb 100644 --- a/cockatrice/src/game/game_event_handler.cpp +++ b/cockatrice/src/game/game_event_handler.cpp @@ -277,6 +277,8 @@ void GameEventHandler::eventSpectatorLeave(const Event_Leave &event, emit spectatorLeft(eventPlayerId); game->getPlayerManager()->removeSpectator(eventPlayerId); + + emitUserEvent(); } void GameEventHandler::eventGameStateChanged(const Event_GameStateChanged &event, @@ -463,9 +465,9 @@ void GameEventHandler::eventJoin(const Event_Join &event, int /*eventPlayerId*/, PlayerLogic *newPlayer = game->getPlayerManager()->addPlayer(playerId, playerInfo.user_info()); emit logJoinPlayer(newPlayer); emit playerJoined(playerInfo); - - emitUserEvent(); } + + emitUserEvent(); } QString GameEventHandler::getLeaveReason(Event_Leave::LeaveReason reason) diff --git a/cockatrice/src/interface/widgets/dialogs/path_safety_warning.cpp b/cockatrice/src/interface/widgets/dialogs/path_safety_warning.cpp deleted file mode 100644 index d8eabd72d..000000000 --- a/cockatrice/src/interface/widgets/dialogs/path_safety_warning.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "path_safety_warning.h" - -#include "../../client/settings/cache_settings.h" - -#include -#include -#include -#include -#include - -class QWidget; - -bool PathSafetyWarning::confirmOutsideProgramDir(QWidget *parent, const QString &path) -{ - if (SettingsCache::instance().getIsPortableBuild()) { - return true; - } - - if (!PathsSettings::isInsideDir(path, QCoreApplication::applicationDirPath())) { - return true; - } - - QMessageBox box(QMessageBox::Warning, QObject::tr("Path inside the program directory"), - QObject::tr("'%1' is inside the Cockatrice program directory.\n\n" - "Files stored there can be deleted by an update or by uninstalling " - "Cockatrice, so this change was not applied. Choose a location outside " - "the program directory.") - .arg(path), - QMessageBox::Ok, parent); - box.setTextFormat(Qt::PlainText); - box.exec(); - - return false; -} diff --git a/cockatrice/src/interface/widgets/dialogs/path_safety_warning.h b/cockatrice/src/interface/widgets/dialogs/path_safety_warning.h deleted file mode 100644 index 058df3e4b..000000000 --- a/cockatrice/src/interface/widgets/dialogs/path_safety_warning.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef COCKATRICE_PATH_SAFETY_WARNING_H -#define COCKATRICE_PATH_SAFETY_WARNING_H - -class QString; -class QWidget; - -namespace PathSafetyWarning -{ - -/** - * @brief Warns when the user tries to configure a data path inside the program directory. - * - * Shows a message box explaining that files kept there can be deleted by an - * update or by uninstalling Cockatrice. Portable builds are always exempt: - * their data lives beside the executables by design. - * - * @param parent The parent widget - * @param path The path the user is trying to configure - * @return true when the path is outside the program directory and safe to apply - */ -bool confirmOutsideProgramDir(QWidget *parent, const QString &path); - -} // namespace PathSafetyWarning - -#endif // COCKATRICE_PATH_SAFETY_WARNING_H diff --git a/cockatrice/src/interface/widgets/settings_page/deck_editor_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/deck_editor_settings_page.cpp index 31178ba4c..2020b2031 100644 --- a/cockatrice/src/interface/widgets/settings_page/deck_editor_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/deck_editor_settings_page.cpp @@ -2,7 +2,6 @@ #include "../../../client/settings/cache_settings.h" #include "../../pixel_map_generator.h" -#include "../dialogs/path_safety_warning.h" #include "update/card_spoiler/spoiler_background_updater.h" #include @@ -364,9 +363,6 @@ void DeckEditorSettingsPage::spoilerPathButtonClicked() if (lsPath.isEmpty()) { return; } - if (!PathSafetyWarning::confirmOutsideProgramDir(this, lsPath + "/spoiler.xml")) { - return; - } mpSpoilerSavePathLineEdit->setText(lsPath + "/spoiler.xml"); SettingsCache::instance().paths().setSpoilerDatabasePath(lsPath + "/spoiler.xml"); diff --git a/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp index fa8938b32..73a0f7be2 100644 --- a/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp @@ -1,7 +1,6 @@ #include "general_settings_page.h" #include "../../../client/settings/cache_settings.h" -#include "../dialogs/path_safety_warning.h" #include "../interface/card_picture_loader/card_picture_loader.h" #include "../main.h" #include "../server/user/user_info_connection.h" @@ -379,9 +378,6 @@ void GeneralSettingsPage::deckPathButtonClicked() if (path.isEmpty()) { return; } - if (!PathSafetyWarning::confirmOutsideProgramDir(this, path)) { - return; - } deckPathEdit->setText(path); SettingsCache::instance().paths().setDeckPath(path); @@ -393,9 +389,6 @@ void GeneralSettingsPage::filtersPathButtonClicked() if (path.isEmpty()) { return; } - if (!PathSafetyWarning::confirmOutsideProgramDir(this, path)) { - return; - } filtersPathEdit->setText(path); SettingsCache::instance().paths().setFiltersPath(path); @@ -407,9 +400,6 @@ void GeneralSettingsPage::replaysPathButtonClicked() if (path.isEmpty()) { return; } - if (!PathSafetyWarning::confirmOutsideProgramDir(this, path)) { - return; - } replaysPathEdit->setText(path); SettingsCache::instance().paths().setReplaysPath(path); @@ -421,9 +411,6 @@ void GeneralSettingsPage::picsPathButtonClicked() if (path.isEmpty()) { return; } - if (!PathSafetyWarning::confirmOutsideProgramDir(this, path)) { - return; - } picsPathEdit->setText(path); SettingsCache::instance().paths().setPicsPath(path); @@ -435,9 +422,6 @@ void GeneralSettingsPage::cardDatabasePathButtonClicked() if (path.isEmpty()) { return; } - if (!PathSafetyWarning::confirmOutsideProgramDir(this, path)) { - return; - } cardDatabasePathEdit->setText(path); SettingsCache::instance().paths().setCardDatabasePath(path); @@ -449,9 +433,6 @@ void GeneralSettingsPage::customCardDatabaseButtonClicked() if (path.isEmpty()) { return; } - if (!PathSafetyWarning::confirmOutsideProgramDir(this, path)) { - return; - } customCardDatabasePathEdit->setText(path); SettingsCache::instance().paths().setCustomCardDatabasePath(path); @@ -463,9 +444,6 @@ void GeneralSettingsPage::tokenDatabasePathButtonClicked() if (path.isEmpty()) { return; } - if (!PathSafetyWarning::confirmOutsideProgramDir(this, path)) { - return; - } tokenDatabasePathEdit->setText(path); SettingsCache::instance().paths().setTokenDatabasePath(path); diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index 4cf5919ba..9a6862b2c 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -1060,12 +1060,6 @@ void TabGame::setCardMenu(CardMenu *menu) return; } - if (aCardMenu->menu() && aCardMenu->menu()->isVisible()) { - aCardMenu->menu()->hide(); - if (gameView && gameView->viewport()) { - gameView->viewport()->update(); - } - } if (menu) { aCardMenu->setMenu(menu); } else { diff --git a/cockatrice/src/interface/widgets/tabs/tab_report.cpp b/cockatrice/src/interface/widgets/tabs/tab_report.cpp index 9018e2477..5f158e9b1 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_report.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_report.cpp @@ -1,6 +1,5 @@ #include "tab_report.h" -#include "../server/user/user_context_menu.h" #include "../utility/report_utils.h" #include "abstract_client.h" #include "libcockatrice/protocol/pb/response.pb.h" @@ -34,13 +33,11 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include #include @@ -123,10 +120,6 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T table->horizontalHeader()->setSectionResizeMode(COL_REPORTED, QHeaderView::Stretch); table->horizontalHeader()->setSectionResizeMode(COL_REPORTER, QHeaderView::ResizeToContents); connect(table, &QTableWidget::itemSelectionChanged, this, &TabReport::onSelectionChanged); - table->setContextMenuPolicy(Qt::CustomContextMenu); - connect(table, &QTableWidget::customContextMenuRequested, this, &TabReport::showTableContextMenu); - userContextMenu = new UserContextMenu(tabSupervisor, this); - connect(userContextMenu, &UserContextMenu::openMessageDialog, this, &TabReport::openMessageDialog); descGroup = new QGroupBox; descriptionEdit = new QTextEdit; @@ -238,14 +231,14 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T assignButton = new QPushButton; resolveButton = new QPushButton; + resolveWithNoteButton = new QPushButton; dismissButton = new QPushButton; - reopenButton = new QPushButton; viewReplayButton = new QPushButton; joinGameButton = new QPushButton; connect(assignButton, &QPushButton::clicked, this, &TabReport::assignReport); - connect(resolveButton, &QPushButton::clicked, this, [this]() { resolveReport(false); }); - connect(dismissButton, &QPushButton::clicked, this, [this]() { resolveReport(true); }); - connect(reopenButton, &QPushButton::clicked, this, &TabReport::reopenReport); + connect(resolveButton, &QPushButton::clicked, this, [this]() { resolveReport(false, false); }); + connect(resolveWithNoteButton, &QPushButton::clicked, this, [this]() { resolveReport(false, true); }); + connect(dismissButton, &QPushButton::clicked, this, [this]() { resolveReport(true, true); }); connect(viewReplayButton, &QPushButton::clicked, this, &TabReport::viewReplay); connect(joinGameButton, &QPushButton::clicked, this, &TabReport::joinGame); @@ -254,8 +247,8 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T auto *actionBar = new QHBoxLayout; actionBar->addWidget(assignButton); actionBar->addWidget(resolveButton); + actionBar->addWidget(resolveWithNoteButton); actionBar->addWidget(dismissButton); - actionBar->addWidget(reopenButton); actionBar->addSpacing(20); actionBar->addWidget(viewReplayButton); actionBar->addWidget(joinGameButton); @@ -279,7 +272,6 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T void TabReport::retranslateUi() { - userContextMenu->retranslateUi(); searchEdit->setPlaceholderText(tr("Search by username, category...")); statusFilter->clear(); statusFilter->addItem(tr("All Statuses"), ""); @@ -320,10 +312,9 @@ void TabReport::retranslateUi() statsGroup->setTitle(tr("Report Statistics")); assignButton->setText(tr("Assign to Me")); - resolveButton->setText(tr("Resolve...")); + resolveButton->setText(tr("Resolve")); + resolveWithNoteButton->setText(tr("Resolve with note...")); dismissButton->setText(tr("Dismiss...")); - reopenButton->setText(tr("Reopen...")); - reopenButton->setToolTip(tr("Available for resolved or dismissed reports.")); viewReplayButton->setText(tr("View Replay")); joinGameButton->setText(tr("Join Game")); } @@ -355,7 +346,6 @@ void TabReport::reportListResponse(const Response &response) refreshButton->setEnabled(true); if (response.response_code() != Response::RespOk) { - pendingStatusMessage.clear(); statusLabel->setText(tr("Failed to load reports.")); return; } @@ -369,11 +359,6 @@ void TabReport::reportListResponse(const Response &response) applyFilters(); updateStats(); - if (!pendingStatusMessage.isEmpty()) { - statusLabel->setText(pendingStatusMessage); - pendingStatusMessage.clear(); - } - if (selectedReportIdBeforeRefresh >= 0) { bool found = false; for (int row = 0; row < table->rowCount(); ++row) { @@ -509,36 +494,6 @@ void TabReport::onSelectionChanged() updateActionStates(); } -void TabReport::showTableContextMenu(const QPoint &pos) -{ - QTableWidgetItem *clickedItem = table->itemAt(pos); - if (!clickedItem || clickedItem->row() < 0) { - return; - } - - const int row = clickedItem->row(); - const QTableWidgetItem *reportedItem = table->item(row, COL_REPORTED); - const QTableWidgetItem *idItem = table->item(row, COL_ID); - if (!reportedItem || !idItem || reportedItem->text().isEmpty()) { - return; - } - - int userLevel = ServerInfo_User::IsUser; - const int reportId = idItem->data(Qt::UserRole).toInt(); - for (const ServerInfo_Report &r : filteredReports) { - if (r.report_id() == reportId) { - if (r.reported_user_level() != 0) { - userLevel = r.reported_user_level(); - } - break; - } - } - - table->selectRow(row); - userContextMenu->showContextMenu(table->viewport()->mapToGlobal(pos), reportedItem->text(), - UserLevelFlags(userLevel)); -} - void TabReport::loadReportDetails(int reportId) { detailsRequestedReportId = reportId; @@ -622,8 +577,8 @@ void TabReport::updateActionStates() const QString status = QString::fromStdString(report.status()); assignButton->setEnabled(status == "open"); resolveButton->setEnabled(status == "open" || status == "assigned"); + resolveWithNoteButton->setEnabled(status == "open" || status == "assigned"); dismissButton->setEnabled(status == "open" || status == "assigned"); - reopenButton->setEnabled(status == "resolved" || status == "dismissed"); const bool hasGameId = report.game_id() > 0; const bool hasReplay = hasGameId && report.has_replay_id() && report.replay_id() > 0; @@ -635,8 +590,8 @@ void TabReport::setActionsEnabled(bool enabled) { assignButton->setEnabled(enabled); resolveButton->setEnabled(enabled); + resolveWithNoteButton->setEnabled(enabled); dismissButton->setEnabled(enabled); - reopenButton->setEnabled(enabled); viewReplayButton->setEnabled(false); joinGameButton->setEnabled(false); commentButton->setEnabled(enabled); @@ -690,7 +645,7 @@ void TabReport::assignReport() void TabReport::assignResponse(const Response &response) { if (response.response_code() == Response::RespOk) { - pendingStatusMessage = tr("Assigned."); + statusLabel->setText(tr("Assigned.")); refreshList(); } else { statusLabel->setText(tr("Assignment failed.")); @@ -698,19 +653,22 @@ void TabReport::assignResponse(const Response &response) } } -void TabReport::resolveReport(bool dismissed) +void TabReport::resolveReport(bool dismissed, bool promptNote) { const int reportId = selectedReportId(); if (reportId < 0) { return; } - bool ok; - QString note = QInputDialog::getText(this, dismissed ? tr("Dismiss Report") : tr("Resolve Report"), - dismissed ? tr("Optional note:") : tr("Resolution note (optional):"), - QLineEdit::Normal, QString(), &ok); - if (!ok) { - return; + QString note; + if (promptNote) { + bool ok; + note = QInputDialog::getText(this, dismissed ? tr("Dismiss Report") : tr("Resolve Report"), + dismissed ? tr("Optional note:") : tr("Resolution note (optional):"), + QLineEdit::Normal, QString(), &ok); + if (!ok) { + return; + } } setActionsEnabled(false); @@ -731,46 +689,7 @@ void TabReport::resolveReport(bool dismissed) void TabReport::resolveResponse(const Response &response) { if (response.response_code() == Response::RespOk) { - pendingStatusMessage = tr("Done."); - refreshList(); - } else { - statusLabel->setText(tr("Action failed.")); - updateActionStates(); - } -} - -void TabReport::reopenReport() -{ - const int reportId = selectedReportId(); - if (reportId < 0) { - return; - } - - bool ok; - QString note = QInputDialog::getText(this, tr("Reopen Report"), tr("Reopening note (optional):"), QLineEdit::Normal, - QString(), &ok); - if (!ok) { - return; - } - - setActionsEnabled(false); - statusLabel->setText(tr("Reopening...")); - - Command_ReportReopen cmd; - cmd.set_report_id(reportId); - if (!note.isEmpty()) { - cmd.set_reopen_note(note.toStdString()); - } - - PendingCommand *pend = client->prepareModeratorCommand(cmd); - connect(pend, &PendingCommand::finished, this, &TabReport::reopenResponse); - client->sendCommand(pend); -} - -void TabReport::reopenResponse(const Response &response) -{ - if (response.response_code() == Response::RespOk) { - pendingStatusMessage = tr("Reopened."); + statusLabel->setText(tr("Done.")); refreshList(); } else { statusLabel->setText(tr("Action failed.")); diff --git a/cockatrice/src/interface/widgets/tabs/tab_report.h b/cockatrice/src/interface/widgets/tabs/tab_report.h index 32ebe1a3f..645f949de 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_report.h +++ b/cockatrice/src/interface/widgets/tabs/tab_report.h @@ -4,7 +4,6 @@ #include "tab.h" #include -#include #include #include #include @@ -23,7 +22,6 @@ class QTimer; class GameReplay; class Response; class TabSupervisor; -class UserContextMenu; class TabReport : public Tab { @@ -39,17 +37,15 @@ public: signals: void openReplay(GameReplay *replay); void requestJoinGame(int gameId, int roomId); - void openMessageDialog(const QString &userName, bool focus); private slots: void refreshList(); void reportListResponse(const Response &response); void assignReport(); void assignResponse(const Response &response); - void resolveReport(bool dismissed); + void resolveReport(bool dismissed, bool promptNote); void resolveResponse(const Response &response); void onSelectionChanged(); - void showTableContextMenu(const QPoint &pos); void viewReplay(); void viewReplayResponse(const Response &response); void joinGame(); @@ -60,8 +56,6 @@ private slots: void reportDetailsResponse(const Response &response); void requestStats(); void statsResponse(const Response &response); - void reopenReport(); - void reopenResponse(const Response &response); private: int selectedReportId() const; @@ -91,13 +85,12 @@ private: QPushButton *commentButton; QPushButton *assignButton; QPushButton *resolveButton; + QPushButton *resolveWithNoteButton; QPushButton *dismissButton; - QPushButton *reopenButton; QPushButton *viewReplayButton; QPushButton *joinGameButton; QLabel *statusLabel; QTimer *refreshTimer; - UserContextMenu *userContextMenu; QGroupBox *userContextGroup; QLabel *userContextName; @@ -115,7 +108,6 @@ private: QLabel *userContextNotesLabel; QLabel *userContextRecentReportsLabel; QString lastRequestedUser; - QString pendingStatusMessage; QGroupBox *statsGroup; QLabel *statsTotalLabel; QLabel *statsTrendLabel; diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp index 2352d7d7b..70014ec47 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp @@ -919,7 +919,6 @@ void TabSupervisor::openTabReport() myAddTab(tabReport, aTabReport); connect(tabReport, &TabReport::openReplay, this, &TabSupervisor::openReplay); connect(tabReport, &TabReport::requestJoinGame, this, &TabSupervisor::joinReportGame); - connect(tabReport, &TabReport::openMessageDialog, this, &TabSupervisor::addMessageTab); connect(tabReport, &QObject::destroyed, this, [this] { tabReport = nullptr; aTabReport->setChecked(false); diff --git a/cockatrice/src/interface/widgets/utility/line_edit_completer.cpp b/cockatrice/src/interface/widgets/utility/line_edit_completer.cpp index d692fb41b..056887f1a 100644 --- a/cockatrice/src/interface/widgets/utility/line_edit_completer.cpp +++ b/cockatrice/src/interface/widgets/utility/line_edit_completer.cpp @@ -130,19 +130,10 @@ void LineEditCompleter::keyPressEvent(QKeyEvent *event) CompleterInfo *active = nullptr; QString prefix; - // An open "[[" card link takes precedence over a mention: while the cursor is - // inside it, an earlier "@" must not shadow the card completer. - const int openCardPos = textValue.lastIndexOf("[[", cursorPos - 1); - const int closeCardPos = openCardPos == -1 ? -1 : textValue.indexOf("]]", openCardPos + 2); - const bool insideCardLink = openCardPos != -1 && (closeCardPos == -1 || closeCardPos >= cursorPos); - for (auto &info : completers) { bool triggered = false; switch (info.trigger) { case CompleterTrigger::Mention: { - if (insideCardLink) { - break; - } int triggerPos = textValue.lastIndexOf("@", cursorPos - 1); if (triggerPos != -1 && (triggerPos == 0 || textValue[triggerPos - 1].isSpace())) { triggered = true; diff --git a/cockatrice/src/interface/widgets/utility/report_utils.cpp b/cockatrice/src/interface/widgets/utility/report_utils.cpp index d7fc083c6..a581c9837 100644 --- a/cockatrice/src/interface/widgets/utility/report_utils.cpp +++ b/cockatrice/src/interface/widgets/utility/report_utils.cpp @@ -100,27 +100,10 @@ void renderReportDetails(QTextEdit *chatLogEdit, const QString &moderatorPrefix, const QString &nonModeratorPrefix) { - QString details; - if (report.has_resolution_note() && !report.resolution_note().empty()) { - details = QObject::tr("Resolution note: %1").arg(QString::fromStdString(report.resolution_note())); - } - if (report.has_resolution_time()) { - QString resolvedBy = QObject::tr("unknown"); - if (report.has_resolved_by_name() && !report.resolved_by_name().empty()) { - resolvedBy = QString::fromStdString(report.resolved_by_name()); - } - if (!details.isEmpty()) { - details += "\n"; - } - details += QObject::tr("Resolved at %1 by %2").arg(formatReportTime(report.resolution_time()), resolvedBy); - } - if (report.has_chat_log() && !report.chat_log().empty()) { - const QString chatLog = QString::fromStdString(report.chat_log()); - chatLogEdit->setPlainText( - details.isEmpty() ? chatLog : details + "\n\n" + QObject::tr("=== Chat Log ===") + "\n" + chatLog); + chatLogEdit->setPlainText(QString::fromStdString(report.chat_log())); } else { - chatLogEdit->setPlainText(details); + chatLogEdit->clear(); } commentsEdit->clear(); diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 4f363181e..189585d84 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -58,9 +58,7 @@ #include #include #include -#include #include -#include #include #include #include @@ -605,39 +603,8 @@ MainWindow::MainWindow(QWidget *parent) QTimer::singleShot(0, this, &MainWindow::applyStartupDestination); } -void MainWindow::warnAboutDataPathsInProgramDir() -{ - auto &paths = SettingsCache::instance().paths(); - if (SettingsCache::instance().getIsPortableBuild() || paths.getAppDirWarningAcknowledged()) { - return; - } - - const QStringList offenders = paths.pathsInsideDir(QCoreApplication::applicationDirPath()); - if (offenders.isEmpty()) { - return; - } - - QMessageBox box(QMessageBox::Warning, tr("Data paths inside the program directory"), - tr("These paths are configured inside the Cockatrice program directory:\n\n%1\n\n" - "Cockatrice keeps its own data outside the program directory by default. Files " - "stored inside it can be deleted by an update or by uninstalling Cockatrice. " - "Change them in the Settings dialog - most are on the General page - to keep " - "them safe.") - .arg(offenders.join(QLatin1Char('\n'))), - QMessageBox::Ok, this); - box.setTextFormat(Qt::PlainText); - auto *dontShowAgain = new QCheckBox(tr("Don't show this again")); - box.setCheckBox(dontShowAgain); - box.exec(); - if (dontShowAgain->isChecked()) { - paths.setAppDirWarningAcknowledged(true); - } -} - void MainWindow::startupConfigCheck() { - warnAboutDataPathsInProgramDir(); - const bool isCleanInstall = SettingsCache::instance().network().getClientVersion() == CLIENT_INFO_NOT_SET; // checkUnknownSets() is intentionally deferred from the card database load diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h index 6b1186d3c..bf68ed72a 100644 --- a/cockatrice/src/interface/window_main.h +++ b/cockatrice/src/interface/window_main.h @@ -149,9 +149,6 @@ private: void runFirstRunWizard(bool firstRun = false); - /** @brief Warns once when a non-portable install keeps its data paths inside the program directory. */ - void warnAboutDataPathsInProgramDir(); - inline QString getCardUpdaterBinaryName() { return "oracle"; diff --git a/gcovr.cfg b/gcovr.cfg deleted file mode 100644 index ac893b6a4..000000000 --- a/gcovr.cfg +++ /dev/null @@ -1,63 +0,0 @@ -# gcovr configuration for Cockatrice line coverage. -# -# Used by .ci/compile.sh --coverage and by hand: -# -# cmake -S . -B build/cover -DCMAKE_BUILD_TYPE=Debug -DWITH_SERVER=1 -DTEST=1 -DENABLE_COVERAGE=ON -# cmake --build build/cover -j 24 -# ctest --test-dir build/cover -LE bench -j 24 -# gcovr build/cover --root . --object-directory build/cover --txt --decisions -# -# The point of most of this file is subtraction. A raw report is dominated by code no test can -# reach: vendored third-party trees, build directories left over from earlier experiments, and -# the client's main() and window classes, which have no headless test. Those all dilute the one -# number worth watching, so they are filtered out here rather than in every invocation. - -# root is deliberately not set here. This file is normally invoked from the build directory -# (compile.sh runs there, because it configures with `cmake ..`), so a `root` of `.` would -# resolve to the build tree and every filter below would exclude everything. compile.sh passes -# --root explicitly instead. - -# Our own code only. This keeps generated Qt MOC output, protobuf output and anything CMake -# built under the source tree out of the report. -filter = ^cockatrice/ -filter = ^libcockatrice_[^/]+/ -filter = ^servatrice/ -filter = ^oracle/ - -# The tests are the vehicle, not the subject. They are compiled with the same instrumentation, -# so leaving them in would add their near-perfect coverage to the numerator and quietly report a -# healthier project than we have: test code is only ever executed by the run that is measuring -# it. Helpers under tests/ are excluded on the same grounds. -exclude = ^tests/ - -# Vendored third-party code is not ours to cover and is never going to move the number. -exclude = ^vcpkg/ -exclude = ^doc/doxygen/theme/ -exclude = ^libcockatrice_rng/libcockatrice/rng/sfmt/ -exclude = ^libcockatrice_utility/libcockatrice/utility/peglib\.h$ -exclude = ^oracle/src/lzma/ -exclude = ^oracle/src/qt-json/ -exclude = ^oracle/src/zip/ -exclude = ^servatrice/src/smtp/ - -# Qt's own moc/pch/uic output is generated into the build tree, not the source tree, so the -# filters above already keep it out. These cover CLion's directories, which sit in-tree and are -# picked up by the object-directory walk. -exclude = .*cmake-build-.* -exclude = .*\.cache/ - -# Process entry points and the client's window chrome. Each of these needs a display, an -# interactive terminal, or a live database before it does anything a test could observe, so no -# headless run will ever move them. Left in, they contribute thousands of lines at 0% and drag -# the total down by a fixed amount that hides movement in the code we can actually test. The -# window header is excluded with its .cpp because gcovr attributes its inline template code -# there, which is where most of its line count lives. -exclude = ^cockatrice/src/main\.cpp$ -exclude = ^cockatrice/src/interface/window_main\. -exclude = ^oracle/src/main\.cpp$ -exclude = ^servatrice/src/main\.cpp$ - -exclude-throw-branches = yes -exclude-function-lines = yes - -print-summary = yes diff --git a/libcockatrice_card/CMakeLists.txt b/libcockatrice_card/CMakeLists.txt index b3583522c..15388ecfc 100644 --- a/libcockatrice_card/CMakeLists.txt +++ b/libcockatrice_card/CMakeLists.txt @@ -60,5 +60,3 @@ target_link_libraries( PUBLIC libcockatrice_utility PUBLIC ${QT_CORE_MODULE} ) - -target_precompile_headers(libcockatrice_card PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtcore_pch.h") diff --git a/libcockatrice_deck_list/CMakeLists.txt b/libcockatrice_deck_list/CMakeLists.txt index e61a5702c..0c487466c 100644 --- a/libcockatrice_deck_list/CMakeLists.txt +++ b/libcockatrice_deck_list/CMakeLists.txt @@ -41,5 +41,3 @@ target_link_libraries( libcockatrice_deck_list PUBLIC libcockatrice_interfaces libcockatrice_protocol libcockatrice_utility ${QT_CORE_MODULE} ) - -target_precompile_headers(libcockatrice_deck_list PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtcore_pch.h") diff --git a/libcockatrice_filters/CMakeLists.txt b/libcockatrice_filters/CMakeLists.txt index 2d45b8e5b..905533df8 100644 --- a/libcockatrice_filters/CMakeLists.txt +++ b/libcockatrice_filters/CMakeLists.txt @@ -18,5 +18,3 @@ add_dependencies(libcockatrice_filters libcockatrice_card) target_include_directories(libcockatrice_filters PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(libcockatrice_filters PUBLIC libcockatrice_card ${QT_CORE_MODULE}) - -target_precompile_headers(libcockatrice_filters PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtcore_pch.h") diff --git a/libcockatrice_interfaces/CMakeLists.txt b/libcockatrice_interfaces/CMakeLists.txt index 3b17569b1..f53c41807 100644 --- a/libcockatrice_interfaces/CMakeLists.txt +++ b/libcockatrice_interfaces/CMakeLists.txt @@ -31,5 +31,3 @@ add_library(libcockatrice_interfaces STATIC ${MOC_SOURCES}) target_include_directories(libcockatrice_interfaces PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(libcockatrice_interfaces PUBLIC libcockatrice_utility ${QT_CORE_MODULE}) - -target_precompile_headers(libcockatrice_interfaces PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtcore_pch.h") diff --git a/libcockatrice_models/libcockatrice/models/database/CMakeLists.txt b/libcockatrice_models/libcockatrice/models/database/CMakeLists.txt index a73e36dbb..840f14b08 100644 --- a/libcockatrice_models/libcockatrice/models/database/CMakeLists.txt +++ b/libcockatrice_models/libcockatrice/models/database/CMakeLists.txt @@ -29,5 +29,3 @@ add_library( target_include_directories(libcockatrice_models_database PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(libcockatrice_models_database PUBLIC libcockatrice_card libcockatrice_filters ${QT_CORE_MODULE}) - -target_precompile_headers(libcockatrice_models_database PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtcore_pch.h") diff --git a/libcockatrice_models/libcockatrice/models/deck_list/CMakeLists.txt b/libcockatrice_models/libcockatrice/models/deck_list/CMakeLists.txt index 324bed102..a6ab2a204 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/CMakeLists.txt +++ b/libcockatrice_models/libcockatrice/models/deck_list/CMakeLists.txt @@ -16,5 +16,3 @@ target_include_directories(libcockatrice_models_deck_list PUBLIC ${CMAKE_CURRENT target_link_libraries( libcockatrice_models_deck_list PUBLIC libcockatrice_card libcockatrice_deck_list ${QT_CORE_MODULE} ) - -target_precompile_headers(libcockatrice_models_deck_list PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtcore_pch.h") diff --git a/libcockatrice_network/libcockatrice/network/client/abstract/CMakeLists.txt b/libcockatrice_network/libcockatrice/network/client/abstract/CMakeLists.txt index 53ad524b4..6fba8d629 100644 --- a/libcockatrice_network/libcockatrice/network/client/abstract/CMakeLists.txt +++ b/libcockatrice_network/libcockatrice/network/client/abstract/CMakeLists.txt @@ -19,5 +19,3 @@ target_link_libraries( PUBLIC ${COCKATRICE_QT_VERSION_NAME}::Network ${COCKATRICE_QT_VERSION_NAME}::WebSockets libcockatrice_protocol libcockatrice_network_server_remote ) - -target_precompile_headers(libcockatrice_network_client_abstract PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtcore_pch.h") diff --git a/libcockatrice_network/libcockatrice/network/client/local/CMakeLists.txt b/libcockatrice_network/libcockatrice/network/client/local/CMakeLists.txt index 08c12b7f8..2ac12e1fe 100644 --- a/libcockatrice_network/libcockatrice/network/client/local/CMakeLists.txt +++ b/libcockatrice_network/libcockatrice/network/client/local/CMakeLists.txt @@ -19,5 +19,3 @@ target_link_libraries( PUBLIC ${COCKATRICE_QT_VERSION_NAME}::Network ${COCKATRICE_QT_VERSION_NAME}::WebSockets libcockatrice_network_client_abstract ) - -target_precompile_headers(libcockatrice_network_client_local PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtcore_pch.h") diff --git a/libcockatrice_network/libcockatrice/network/client/remote/CMakeLists.txt b/libcockatrice_network/libcockatrice/network/client/remote/CMakeLists.txt index c32ca0aa8..cb68d0c37 100644 --- a/libcockatrice_network/libcockatrice/network/client/remote/CMakeLists.txt +++ b/libcockatrice_network/libcockatrice/network/client/remote/CMakeLists.txt @@ -19,5 +19,3 @@ target_link_libraries( PUBLIC ${COCKATRICE_QT_VERSION_NAME}::Network ${COCKATRICE_QT_VERSION_NAME}::WebSockets libcockatrice_network_client_abstract libcockatrice_interfaces libcockatrice_utility libcockatrice_protocol ) - -target_precompile_headers(libcockatrice_network_client_remote PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtcore_pch.h") diff --git a/libcockatrice_network/libcockatrice/network/server/local/CMakeLists.txt b/libcockatrice_network/libcockatrice/network/server/local/CMakeLists.txt index 020753d7f..494ae1294 100644 --- a/libcockatrice_network/libcockatrice/network/server/local/CMakeLists.txt +++ b/libcockatrice_network/libcockatrice/network/server/local/CMakeLists.txt @@ -15,5 +15,3 @@ add_dependencies(libcockatrice_network_server_local libcockatrice_protocol) target_include_directories(libcockatrice_network_server_local PUBLIC .) target_link_libraries(libcockatrice_network_server_local PUBLIC ${COCKATRICE_QT_MODULES} libcockatrice_protocol) - -target_precompile_headers(libcockatrice_network_server_local PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtcore_pch.h") diff --git a/libcockatrice_network/libcockatrice/network/server/remote/CMakeLists.txt b/libcockatrice_network/libcockatrice/network/server/remote/CMakeLists.txt index 5840f80c0..e11a962d1 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/CMakeLists.txt +++ b/libcockatrice_network/libcockatrice/network/server/remote/CMakeLists.txt @@ -62,5 +62,3 @@ target_link_libraries( libcockatrice_network_server_remote PUBLIC libcockatrice_protocol libcockatrice_utility libcockatrice_rng libcockatrice_deck_list ${COCKATRICE_QT_MODULES} ) - -target_precompile_headers(libcockatrice_network_server_remote PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtcore_pch.h") diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h index ae20955bf..eff5034b4 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.h @@ -56,10 +56,6 @@ public: { return deck; } - bool getSideboardLocked() const - { - return sideboardLocked; - } bool getReadyStart() const { return readyStart; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp index b1863c91e..330d473b2 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -61,8 +61,6 @@ #include #include #include -#include -#include #include #include #include @@ -827,48 +825,6 @@ void Server_Game::createGameJoinedEvent(Server_AbstractParticipant *joiningParti } rc.enqueuePostResponseItem(ServerMessage::GAME_EVENT_CONTAINER, prepareGameEvent(event2, -1)); - - // A newly joined player never saw the "Player X has loaded a deck" messages - // (they are only broadcast when a deck is selected), so replay a deck-select - // context for every player that already has a deck loaded. It is delivered - // privately to the joining participant, whose client renders it as a log - // entry in the same way as a fresh deck selection. Skipped on reconnect - // (resuming) — the player already saw these — and once the game has started, - // when the message is no longer useful and would only spam the log. - if (!resuming && !gameStarted) { - for (auto *participant : participants.values()) { - if (participant == joiningParticipant) { - continue; - } - auto *deckOwner = qobject_cast(participant); - if (deckOwner == nullptr) { - continue; - } - const DeckList *deckList = deckOwner->getDeckList(); - if (deckList == nullptr) { - continue; - } - - Event_PlayerPropertiesChanged event; - event.mutable_player_properties()->set_sideboard_locked(deckOwner->getSideboardLocked()); - event.mutable_player_properties()->set_deck_hash(deckList->getDeckHash().toStdString()); - - Context_DeckSelect deckSelect; - deckSelect.set_deck_hash(deckList->getDeckHash().toStdString()); - deckSelect.set_sideboard_size(deckList->getSideboardSize()); - if (getShareDecklistsOnLoad()) { - deckSelect.set_deck_list(deckList->writeToString_Native().toStdString()); - } - - GameEventContext context; - context.GetReflection() - ->MutableMessage(&context, deckSelect.GetDescriptor()->FindExtensionByName("ext")) - ->CopyFrom(deckSelect); - - rc.enqueuePostResponseItem(ServerMessage::GAME_EVENT_CONTAINER, - prepareGameEvent(event, deckOwner->getPlayerId(), &context)); - } - } } void Server_Game::sendGameEventContainer(GameEventContainer *cont, diff --git a/libcockatrice_protocol/CMakeLists.txt b/libcockatrice_protocol/CMakeLists.txt index 55184045a..7dc0a0360 100644 --- a/libcockatrice_protocol/CMakeLists.txt +++ b/libcockatrice_protocol/CMakeLists.txt @@ -24,5 +24,3 @@ target_include_directories( libcockatrice_protocol PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} # points to the generated headers ) - -target_precompile_headers(libcockatrice_protocol PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtcore_pch.h") diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt b/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt index 4e4f0e190..bc4814d5e 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt @@ -55,7 +55,6 @@ set(PROTO_FILES command_report_details.proto command_report_list.proto command_report_my_list.proto - command_report_reopen.proto command_report_resolve.proto command_report_stats.proto command_report_user_info.proto diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/command_report_reopen.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/command_report_reopen.proto deleted file mode 100644 index 1f9c707e8..000000000 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/command_report_reopen.proto +++ /dev/null @@ -1,10 +0,0 @@ -syntax = "proto2"; -import "moderator_commands.proto"; - -message Command_ReportReopen { - extend ModeratorCommand { - optional Command_ReportReopen ext = 1206; - } - required int32 report_id = 1; - optional string reopen_note = 2; -} diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/moderator_commands.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/moderator_commands.proto index 5298d3dad..4f1e80c27 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/moderator_commands.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/moderator_commands.proto @@ -26,7 +26,6 @@ message ModeratorCommand { REPLAY_DOWNLOAD_BY_GAME_ID = 1203; REPORT_USER_INFO = 1204; REPORT_STATS = 1205; - REPORT_REOPEN = 1206; } extensions 100 to max; } diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_report.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_report.proto index 3b624cc6d..a145fd855 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_report.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_report.proto @@ -33,7 +33,4 @@ message ServerInfo_Report { optional string chat_log = 14; optional string resolution_note = 15; - optional string resolved_by_name = 16; - - optional int32 reported_user_level = 17; } diff --git a/libcockatrice_rng/CMakeLists.txt b/libcockatrice_rng/CMakeLists.txt index 6128e3f6e..b988c9d2f 100644 --- a/libcockatrice_rng/CMakeLists.txt +++ b/libcockatrice_rng/CMakeLists.txt @@ -11,10 +11,6 @@ add_library( libcockatrice/rng/sfmt/SFMT.c ) -# SFMT is vendored and its own headers trip UBSan's alignment and vptr checks. -cockatrice_disable_sanitizers_for(libcockatrice/rng/sfmt/SFMT.c) -cockatrice_disable_coverage_for(libcockatrice/rng/sfmt/SFMT.c) - target_include_directories(libcockatrice_rng PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(libcockatrice_rng PUBLIC ${QT_CORE_MODULE}) diff --git a/libcockatrice_settings/CMakeLists.txt b/libcockatrice_settings/CMakeLists.txt index 5a2fa42a9..80a465b6d 100644 --- a/libcockatrice_settings/CMakeLists.txt +++ b/libcockatrice_settings/CMakeLists.txt @@ -71,5 +71,3 @@ target_include_directories( ) target_link_libraries(libcockatrice_settings PUBLIC libcockatrice_card libcockatrice_utility ${QT_CORE_MODULE}) - -target_precompile_headers(libcockatrice_settings PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtcore_pch.h") diff --git a/libcockatrice_settings/libcockatrice/settings/paths_settings.cpp b/libcockatrice_settings/libcockatrice/settings/paths_settings.cpp index 41ccf6460..5f5725395 100644 --- a/libcockatrice_settings/libcockatrice/settings/paths_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/paths_settings.cpp @@ -2,8 +2,6 @@ #include "libcockatrice/settings/settings_manager.h" -#include -#include #include class QObject; @@ -123,52 +121,3 @@ void PathsSettings::setSpoilerDatabasePath(const QString &_spoilerDatabasePath) setValue(_spoilerDatabasePath, "spoilerDatabase"); emit cardDatabasePathChanged(); } - -QStringList PathsSettings::pathsInsideDir(const QString &baseDir) const -{ - QStringList offenders; - const auto check = [&offenders, &baseDir](const QString &path) { - if (!path.isEmpty() && isInsideDir(path, baseDir)) { - offenders.append(QDir::cleanPath(path)); - } - }; - - check(getDeckPath()); - check(getFiltersPath()); - check(getReplaysPath()); - check(getPicsPath()); - check(getCustomPicsPath()); - check(getThemesPath()); - check(getCardDatabasePath()); - check(getCustomCardDatabasePath()); - check(getTokenDatabasePath()); - check(getSpoilerCardDatabasePath()); - check(getRedirectCachePath()); - - offenders.removeDuplicates(); - return offenders; -} - -bool PathsSettings::isInsideDir(const QString &path, const QString &baseDir) -{ -#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) - // Windows and default macOS volumes are case-insensitive filesystems. - constexpr Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive; -#else - constexpr Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive; -#endif - const QString base = QDir::cleanPath(baseDir); - const QString cleaned = QDir::cleanPath(path); - const QString prefix = base.endsWith(QLatin1Char('/')) ? base : base + QLatin1Char('/'); - return cleaned == base || cleaned.startsWith(prefix, caseSensitivity); -} - -bool PathsSettings::getAppDirWarningAcknowledged() const -{ - return getValue("appDirWarningAcknowledged").toBool(); -} - -void PathsSettings::setAppDirWarningAcknowledged(bool _appDirWarningAcknowledged) -{ - setValue(_appDirWarningAcknowledged, "appDirWarningAcknowledged"); -} diff --git a/libcockatrice_settings/libcockatrice/settings/paths_settings.h b/libcockatrice_settings/libcockatrice/settings/paths_settings.h index fa00428e3..399337bbb 100644 --- a/libcockatrice_settings/libcockatrice/settings/paths_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/paths_settings.h @@ -4,7 +4,6 @@ #include "settings_manager.h" #include -#include #include #include @@ -28,24 +27,6 @@ public: [[nodiscard]] QString getSpoilerCardDatabasePath() const override; [[nodiscard]] QString getRedirectCachePath() const override; - /** - * @brief Returns the configured paths that resolve inside baseDir. - * - * Comparison is lexical (QDir::cleanPath); symlinks are not resolved, so a - * path that only reaches baseDir through a symlink is not reported. - */ - [[nodiscard]] QStringList pathsInsideDir(const QString &baseDir) const; - - /** - * @brief Whether path resolves inside baseDir, as a single-path check. - * - * A path counts as inside when it equals baseDir or is nested below it; - * a sibling sharing the name prefix (baseDir + "2") does not. - */ - [[nodiscard]] static bool isInsideDir(const QString &path, const QString &baseDir); - - [[nodiscard]] bool getAppDirWarningAcknowledged() const; - void setDeckPath(const QString &_deckPath); void setFiltersPath(const QString &_filtersPath); void setReplaysPath(const QString &_replaysPath); @@ -56,17 +37,14 @@ public: void setCustomCardDatabasePath(const QString &_customCardDatabasePath); void setTokenDatabasePath(const QString &_tokenDatabasePath); void setSpoilerDatabasePath(const QString &_spoilerDatabasePath); - void setAppDirWarningAcknowledged(bool _appDirWarningAcknowledged); signals: void cardDatabasePathChanged(); void picsPathChanged(); void themeChanged(); -public: - explicit PathsSettings(const QString &settingPath, QObject *parent = nullptr); - private: + explicit PathsSettings(const QString &settingPath, QObject *parent = nullptr); PathsSettings(const PathsSettings & /*other*/); }; diff --git a/libcockatrice_utility/CMakeLists.txt b/libcockatrice_utility/CMakeLists.txt index 5e6fa986d..d986214d7 100644 --- a/libcockatrice_utility/CMakeLists.txt +++ b/libcockatrice_utility/CMakeLists.txt @@ -49,5 +49,3 @@ target_link_libraries(libcockatrice_utility PUBLIC libcockatrice_rng OpenSSL::Cr set(ORACLE_LIBS) include_directories(${${COCKATRICE_QT_VERSION_NAME}Core_INCLUDE_DIRS}) - -target_precompile_headers(libcockatrice_utility PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtcore_pch.h") diff --git a/oracle/CMakeLists.txt b/oracle/CMakeLists.txt index 0df345812..0003b77ac 100644 --- a/oracle/CMakeLists.txt +++ b/oracle/CMakeLists.txt @@ -79,9 +79,6 @@ if(ZLIB_FOUND) include_directories(${ZLIB_INCLUDE_DIRS}) add_definitions("-DHAS_ZLIB") list(APPEND oracle_SOURCES src/zip/unzip.cpp src/zip/zipglobal.cpp) - # minizip and zlib are vendored. - cockatrice_disable_sanitizers_for(src/zip/unzip.cpp src/zip/zipglobal.cpp) - cockatrice_disable_coverage_for(src/zip/unzip.cpp src/zip/zipglobal.cpp) else() message(STATUS "Oracle: zlib not found; ZIP support disabled") endif() @@ -92,9 +89,6 @@ if(LIBLZMA_FOUND) include_directories(${LIBLZMA_INCLUDE_DIRS}) add_definitions("-DHAS_LZMA") list(APPEND oracle_SOURCES src/lzma/decompress.cpp) - # The LZMA SDK is vendored. - cockatrice_disable_sanitizers_for(src/lzma/decompress.cpp) - cockatrice_disable_coverage_for(src/lzma/decompress.cpp) else() message(STATUS "Oracle: LibLZMA not found; xz support disabled") endif() diff --git a/servatrice/CMakeLists.txt b/servatrice/CMakeLists.txt index b45608d86..21f71a908 100644 --- a/servatrice/CMakeLists.txt +++ b/servatrice/CMakeLists.txt @@ -27,14 +27,6 @@ set(servatrice_SOURCES set(servatrice_RESOURCES servatrice.qrc) -# Qxt SMTP is vendored. -cockatrice_disable_sanitizers_for( - src/smtp/qxthmac.cpp src/smtp/qxtmailattachment.cpp src/smtp/qxtmailmessage.cpp src/smtp/qxtsmtp.cpp -) -cockatrice_disable_coverage_for( - src/smtp/qxthmac.cpp src/smtp/qxtmailattachment.cpp src/smtp/qxtmailmessage.cpp src/smtp/qxtsmtp.cpp -) - if(WIN32) set(servatrice_SOURCES ${servatrice_SOURCES} servatrice.rc) endif(WIN32) diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index 5f5d1ced5..d46a9b0ee 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -110,7 +110,6 @@ #include #include #include -#include #include #include #include @@ -435,8 +434,6 @@ Response::ResponseCode AbstractServerSocketInterface::processExtendedModeratorCo return cmdReportAssign(cmd.GetExtension(Command_ReportAssign::ext), rc); case ModeratorCommand::REPORT_RESOLVE: return cmdReportResolve(cmd.GetExtension(Command_ReportResolve::ext), rc); - case ModeratorCommand::REPORT_REOPEN: - return cmdReportReopen(cmd.GetExtension(Command_ReportReopen::ext), rc); case ModeratorCommand::VIEWLOG_HISTORY: return cmdGetLogHistory(cmd.GetExtension(Command_ViewLogHistory::ext), rc, true); case ModeratorCommand::GRANT_REPLAY_ACCESS: @@ -1986,29 +1983,6 @@ Response::ResponseCode AbstractServerSocketInterface::cmdBanFromServer(const Com return Response::RespOk; } -namespace -{ -int reportUserLevel(bool userExists, int adminFlags) -{ - int userLevel = ServerInfo_User::IsUser; - if (userExists) { - userLevel |= ServerInfo_User::IsRegistered; - if (adminFlags & 1) { - userLevel |= ServerInfo_User::IsAdmin | ServerInfo_User::IsModerator; - } else if (adminFlags & 2) { - userLevel |= ServerInfo_User::IsModerator; - } - if (adminFlags & 4) { - userLevel |= ServerInfo_User::IsJudge; - } - if (adminFlags & 8) { - userLevel |= ServerInfo_User::IsDeveloper; - } - } - return userLevel; -} -} // namespace - Response::ResponseCode AbstractServerSocketInterface::cmdReportList(const Command_ReportList &cmd, ResponseContainer &rc) { @@ -2022,9 +1996,8 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportList(const Comman // Columns: 0=id, 1=reporter_name, 2=reported_user_name, 3=game_id, // 4=category, 5=description, 6=created_at, 7=status, - // 8=resolution_note, 9=assigned_mod_name, 10=resolved_by_name, - // 11=room_id, 12=replay_id, - // 13=reported_user_id, 14=reported_user_admin + // 8=resolution_note, 9=assigned_mod_name, + // 10=room_id, 11=replay_id QString whereClause; if (unresolvedOnly) { whereClause = "WHERE r.status = 'open' OR r.status = 'assigned' "; @@ -2039,13 +2012,11 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportList(const Comman QString queryStr = "SELECT r.id, r.reporter_name, r.reported_user_name, r.game_id, " "r.category, r.description, r.created_at, r.status, " - "r.resolution_note, u.name AS assigned_mod_name, ru.name AS resolved_by_name, r.room_id, " + "r.resolution_note, u.name AS assigned_mod_name, r.room_id, " "(SELECT id FROM {prefix}_replays WHERE id_game = r.game_id ORDER BY id DESC LIMIT 1) " - "AS replay_id, u2.id AS reported_user_id, u2.admin AS reported_user_admin " + "AS replay_id " "FROM {prefix}_reports r " - "LEFT JOIN {prefix}_users u ON r.assigned_to = u.id " - "LEFT JOIN {prefix}_users ru ON r.resolved_by = ru.id " - "LEFT JOIN {prefix}_users u2 ON u2.name = r.reported_user_name "; + "LEFT JOIN {prefix}_users u ON r.assigned_to = u.id "; if (unresolvedOnly) { queryStr += "WHERE r.status = 'open' OR r.status = 'assigned' "; @@ -2086,19 +2057,14 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportList(const Comman if (!query->value(9).isNull()) { info->set_assigned_mod_name(query->value(9).toString().toStdString()); } + if (!query->value(10).isNull()) { - info->set_resolved_by_name(query->value(10).toString().toStdString()); + info->set_room_id(query->value(10).toInt()); } if (!query->value(11).isNull()) { - info->set_room_id(query->value(11).toInt()); + info->set_replay_id(query->value(11).toInt()); } - - if (!query->value(12).isNull()) { - info->set_replay_id(query->value(12).toInt()); - } - - info->set_reported_user_level(reportUserLevel(!query->value(13).isNull(), query->value(14).toInt())); } rc.setResponseExtension(re); @@ -2771,13 +2737,11 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportMyList(const Comm QString queryStr = "SELECT r.id, r.reporter_name, r.reported_user_name, r.game_id, " "r.category, r.description, r.created_at, r.status, " - "r.resolution_note, u.name AS assigned_mod_name, ru.name AS resolved_by_name, r.room_id, " + "r.resolution_note, u.name AS assigned_mod_name, r.room_id, " "(SELECT id FROM {prefix}_replays WHERE id_game = r.game_id ORDER BY id DESC LIMIT 1) " - "AS replay_id, u2.id AS reported_user_id, u2.admin AS reported_user_admin " + "AS replay_id " "FROM {prefix}_reports r " "LEFT JOIN {prefix}_users u ON r.assigned_to = u.id " - "LEFT JOIN {prefix}_users ru ON r.resolved_by = ru.id " - "LEFT JOIN {prefix}_users u2 ON u2.name = r.reported_user_name " "WHERE r.reporter_id = :reporter_id " "ORDER BY r.created_at DESC LIMIT 200"; @@ -2813,61 +2777,20 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportMyList(const Comm if (!query->value(9).isNull()) { info->set_assigned_mod_name(query->value(9).toString().toStdString()); } + if (!query->value(10).isNull()) { - info->set_resolved_by_name(query->value(10).toString().toStdString()); + info->set_room_id(query->value(10).toInt()); } if (!query->value(11).isNull()) { - info->set_room_id(query->value(11).toInt()); + info->set_replay_id(query->value(11).toInt()); } - - if (!query->value(12).isNull()) { - info->set_replay_id(query->value(12).toInt()); - } - - info->set_reported_user_level(reportUserLevel(!query->value(13).isNull(), query->value(14).toInt())); } rc.setResponseExtension(re); return Response::RespOk; } -Response::ResponseCode AbstractServerSocketInterface::cmdReportReopen(const Command_ReportReopen &cmd, - ResponseContainer & /*rc*/) -{ - if (!sqlInterface->checkSql()) { - return Response::RespInternalError; - } - - int reportId = cmd.report_id(); - QString note = textFromStdString(cmd.reopen_note()); - - QSqlQuery *query = sqlInterface->prepareQuery("UPDATE {prefix}_reports " - "SET status = 'open', resolution_note = NULL, " - "resolution_time = NULL, resolved_by = NULL, assigned_to = NULL, " - "notified = 0 " - "WHERE id = :id AND status IN ('resolved', 'dismissed')"); - - query->bindValue(":id", reportId); - - if (!sqlInterface->execSqlQuery(query)) { - return Response::RespInternalError; - } - - if (query->numRowsAffected() == 0) { - return Response::RespInvalidData; - } - - QString auditDetails = QString("Report #%1 reopened").arg(reportId); - if (!note.isEmpty()) { - auditDetails += QString(". Reason: %1").arg(note.left(200)); - } - sqlInterface->addAuditRecord(QString::number(reportId), this->getAddress(), - QString::fromStdString(userInfo->clientid()), "REPORT_REOPENED", auditDetails, true); - - return Response::RespOk; -} - Response::ResponseCode AbstractServerSocketInterface::cmdReportDetails(const Command_ReportDetails &cmd, ResponseContainer &rc) { @@ -2883,19 +2806,17 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportDetails(const Com // Columns: 0=id, 1=reporter_id, 2=reporter_name, 3=reported_user_name, 4=game_id, // 5=category, 6=description, 7=created_at, 8=status, - // 9=resolution_note, 10=assigned_mod_name, 11=resolved_by_name, - // 12=chat_log, 13=room_id, 14=resolution_time, 15=replay_id - QString queryStr = - "SELECT r.id, r.reporter_id, r.reporter_name, r.reported_user_name, r.game_id, " - "r.category, r.description, r.created_at, r.status, " - "r.resolution_note, u.name AS assigned_mod_name, ru.name AS resolved_by_name, r.chat_log, r.room_id, " - "r.resolution_time, " - "(SELECT id FROM {prefix}_replays WHERE id_game = r.game_id ORDER BY id DESC LIMIT 1) " - "AS replay_id " - "FROM {prefix}_reports r " - "LEFT JOIN {prefix}_users u ON r.assigned_to = u.id " - "LEFT JOIN {prefix}_users ru ON r.resolved_by = ru.id " - "WHERE r.id = :id"; + // 9=resolution_note, 10=assigned_mod_name, 11=chat_log, + // 12=room_id, 13=resolution_time, 14=replay_id + QString queryStr = "SELECT r.id, r.reporter_id, r.reporter_name, r.reported_user_name, r.game_id, " + "r.category, r.description, r.created_at, r.status, " + "r.resolution_note, u.name AS assigned_mod_name, r.chat_log, r.room_id, " + "r.resolution_time, " + "(SELECT id FROM {prefix}_replays WHERE id_game = r.game_id ORDER BY id DESC LIMIT 1) " + "AS replay_id " + "FROM {prefix}_reports r " + "LEFT JOIN {prefix}_users u ON r.assigned_to = u.id " + "WHERE r.id = :id"; QSqlQuery *query = sqlInterface->prepareQuery(queryStr); query->bindValue(":id", reportId); @@ -2934,24 +2855,21 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReportDetails(const Com if (!query->value(10).isNull()) { info->set_assigned_mod_name(query->value(10).toString().toStdString()); } + if (!query->value(11).isNull()) { - info->set_resolved_by_name(query->value(11).toString().toStdString()); + info->set_chat_log(query->value(11).toString().toStdString()); } if (!query->value(12).isNull()) { - info->set_chat_log(query->value(12).toString().toStdString()); + info->set_room_id(query->value(12).toInt()); } if (!query->value(13).isNull()) { - info->set_room_id(query->value(13).toInt()); + info->set_resolution_time(query->value(13).toDateTime().toSecsSinceEpoch()); } if (!query->value(14).isNull()) { - info->set_resolution_time(query->value(14).toDateTime().toSecsSinceEpoch()); - } - - if (!query->value(15).isNull()) { - info->set_replay_id(query->value(15).toInt()); + info->set_replay_id(query->value(14).toInt()); } QSqlQuery *commentQuery = diff --git a/servatrice/src/serversocketinterface.h b/servatrice/src/serversocketinterface.h index 7de742c66..27708ed27 100644 --- a/servatrice/src/serversocketinterface.h +++ b/servatrice/src/serversocketinterface.h @@ -96,7 +96,6 @@ class Command_ReportAssign; class Command_ReportDetails; class Command_ReportList; class Command_ReportMyList; -class Command_ReportReopen; class Command_ReportResolve; class Command_ReportStats; class Command_ReportUserInfo; @@ -182,7 +181,6 @@ private: Response::ResponseCode cmdUpdateServerMessage(const Command_UpdateServerMessage &cmd, ResponseContainer &rc); Response::ResponseCode cmdReportAssign(const Command_ReportAssign &cmd, ResponseContainer &); Response::ResponseCode cmdReportResolve(const Command_ReportResolve &cmd, ResponseContainer &); - Response::ResponseCode cmdReportReopen(const Command_ReportReopen &cmd, ResponseContainer &); Response::ResponseCode cmdReportUserInfo(const Command_ReportUserInfo &cmd, ResponseContainer &rc); Response::ResponseCode cmdReportStats(const Command_ReportStats &cmd, ResponseContainer &rc); Response::ResponseCode cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 22dbedb98..2c288c6ce 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,10 +2,109 @@ # If a new test needs additional Qt modules, add them there — not in individual test CMakeLists.txt files. enable_testing() -include(GoogleTest) +add_test(NAME dummy_test COMMAND dummy_test) +add_test(NAME expression_test COMMAND expression_test) +add_test(NAME clamped_arithmetic_test COMMAND clamped_arithmetic_test) +add_test(NAME test_age_formatting COMMAND test_age_formatting) +add_test(NAME password_hash_test COMMAND password_hash_test) +add_test(NAME playmat_resolver_test COMMAND playmat_resolver_test) +add_test(NAME server_card_counter_test COMMAND server_card_counter_test) +add_test(NAME server_counter_test COMMAND server_counter_test) +add_test(NAME server_rate_limiter_test COMMAND server_rate_limiter_test) +add_test(NAME server_developer_role_test COMMAND server_developer_role_test) +add_test(NAME server_game_join_test COMMAND server_game_join_test) +add_test(NAME warning_categories_test COMMAND warning_categories_test) +# The translation loader test compiles a .qm fixture, which needs lrelease from Qt Linguist. +if(TARGET Qt6::lrelease) + add_test(NAME translation_loader_test COMMAND translation_loader_test) +endif() +add_test(NAME report_categories_test COMMAND report_categories_test) +add_test(NAME lag_monitor_test COMMAND lag_monitor_test) +add_test(NAME latency_tracker_test COMMAND latency_tracker_test) +add_test(NAME metrics_registry_test COMMAND metrics_registry_test) +add_test(NAME card_update_progress_test COMMAND card_update_progress_test) +add_test(NAME loader_local_matching_test COMMAND loader_local_matching_test) + +add_test(NAME deck_hash_performance_test COMMAND deck_hash_performance_test) +set_tests_properties(deck_hash_performance_test PROPERTIES TIMEOUT 15) # Find GTest +add_executable(dummy_test dummy_test.cpp) +add_executable(expression_test expression_test.cpp) +add_executable(clamped_arithmetic_test clamped_arithmetic_test.cpp) +add_executable(test_age_formatting test_age_formatting.cpp) +add_executable(password_hash_test password_hash_test.cpp) +add_executable(playmat_resolver_test playmat_resolver_test.cpp) +add_executable(deck_hash_performance_test deck_hash_performance_test.cpp) +add_executable(server_card_counter_test server_card_counter_test.cpp) +add_executable(server_counter_test server_counter_test.cpp) +add_executable(server_rate_limiter_test server_rate_limiter_test.cpp) +add_executable(server_developer_role_test server_developer_role_test.cpp) +add_executable(server_game_join_test server_game_join_test.cpp) +add_executable(warning_categories_test warning_categories_test.cpp) +if(TARGET Qt6::lrelease) + add_executable(translation_loader_test translation_loader_test.cpp) + + # QTranslator only accepts real .qm files, so the translation loader test gets one compiled from its + # own .ts fixture rather than a stub. + set(TRANSLATION_LOADER_TEST_QM "${CMAKE_CURRENT_BINARY_DIR}/translation_loader_test_de.qm") + add_custom_command( + OUTPUT "${TRANSLATION_LOADER_TEST_QM}" + COMMAND Qt6::lrelease "${CMAKE_CURRENT_SOURCE_DIR}/translation_loader_test.ts" -qm "${TRANSLATION_LOADER_TEST_QM}" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/translation_loader_test.ts" + COMMENT "Compiling translation_loader_test.ts" + ) + add_custom_target(translation_loader_test_qm DEPENDS "${TRANSLATION_LOADER_TEST_QM}") + add_dependencies(translation_loader_test translation_loader_test_qm) + target_compile_definitions(translation_loader_test PRIVATE TRANSLATION_LOADER_TEST_DIR="${CMAKE_CURRENT_BINARY_DIR}") + + # A multi-config generator (MSBuild, Xcode) puts the executable in a per-config subdirectory while + # the .qm stays in the current binary directory, so copy the fixture next to the binary: that is the + # layout ABuildTreeFindsTheTranslationsNextToTheExecutable is about, and the only one it finds on + # those generators. + add_custom_command( + TARGET translation_loader_test + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${TRANSLATION_LOADER_TEST_QM}" + "$/" + VERBATIM + ) +endif() +add_executable(report_categories_test report_categories_test.cpp) +add_executable(lag_monitor_test ${CMAKE_SOURCE_DIR}/cockatrice/src/client/lag_monitor.cpp lag_monitor_test.cpp) +target_include_directories(lag_monitor_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src) +add_executable(latency_tracker_test latency_tracker_test.cpp) +add_executable(metrics_registry_test ../servatrice/src/metrics_registry.cpp metrics_registry_test.cpp) +add_executable( + card_update_progress_test ${CMAKE_SOURCE_DIR}/cockatrice/src/interface/card_database_update/card_update_progress.cpp + card_update_progress_test.cpp +) +target_include_directories(card_update_progress_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src) +add_executable( + loader_local_matching_test + ${CMAKE_SOURCE_DIR}/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp + ${CMAKE_SOURCE_DIR}/cockatrice/src/client/settings/cache_settings.cpp + ${CMAKE_SOURCE_DIR}/cockatrice/src/client/settings/card_counter_settings.cpp + ${CMAKE_SOURCE_DIR}/cockatrice/src/client/settings/shortcuts_settings.cpp + ${CMAKE_SOURCE_DIR}/cockatrice/src/client/network/update/client/release_channel.cpp + ${VERSION_STRING_CPP} + loader_local_matching_test.cpp +) +target_include_directories(loader_local_matching_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src) + +# Guard must match the condition for src/single_instance_manager.cpp in cockatrice/CMakeLists.txt. +if(WITH_CLIENT) + add_executable( + single_instance_manager_test ${CMAKE_SOURCE_DIR}/cockatrice/src/single_instance_manager.cpp + single_instance_manager_test.cpp + ) + target_include_directories(single_instance_manager_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src) + target_include_directories(single_instance_manager_test PRIVATE ${CMAKE_SOURCE_DIR}/libcockatrice_utility) + + add_test(NAME single_instance_manager_test COMMAND single_instance_manager_test) +endif() + find_package(GTest) if(NOT GTEST_FOUND) @@ -30,342 +129,93 @@ if(NOT GTEST_FOUND) target_include_directories(gtest INTERFACE "$") set(GTEST_INCLUDE_DIRS "${CMAKE_BINARY_DIR}/gtest-src/include") - # gtest_main is not optional here. FindGTest sets GTEST_BOTH_LIBRARIES to both halves, so this - # has to match it or the two paths disagree about what provides main(). A test source that - # defines its own main() gets away with the omission; one that does not fails at link with - # `undefined reference to main` pointing at crt1.o, which names neither gtest nor this line. - set(GTEST_BOTH_LIBRARIES gtest gtest_main) + set(GTEST_BOTH_LIBRARIES gtest) + add_dependencies(dummy_test gtest) + add_dependencies(expression_test gtest) + add_dependencies(clamped_arithmetic_test gtest) + add_dependencies(test_age_formatting gtest) + add_dependencies(password_hash_test gtest) + add_dependencies(playmat_resolver_test gtest) + add_dependencies(deck_hash_performance_test gtest) + add_dependencies(server_card_counter_test gtest) + add_dependencies(server_counter_test gtest) + add_dependencies(server_rate_limiter_test gtest) + add_dependencies(server_developer_role_test gtest) + add_dependencies(server_game_join_test gtest) + add_dependencies(warning_categories_test gtest) + if(TARGET translation_loader_test) + add_dependencies(translation_loader_test gtest) + endif() + add_dependencies(report_categories_test gtest) + add_dependencies(lag_monitor_test gtest) + add_dependencies(latency_tracker_test gtest) + add_dependencies(metrics_registry_test gtest) + add_dependencies(card_update_progress_test gtest) + add_dependencies(loader_local_matching_test gtest) + add_dependencies(single_instance_manager_test gtest) endif() -# Directory-scoped on purpose: the test subdirectories below add targets that include the -# fetched gtest headers and do not repeat this path themselves. include_directories(${GTEST_INCLUDE_DIRS}) - -# --- Test registration ---------------------------------------------------------- -# -# Every test target needs the same four steps: build the executable, depend on gtest when it -# was fetched from source, link the shared test libraries, and hand the cases to CTest. The -# `if(NOT GTEST_FOUND)` branch is the easy one to forget — omit it and the link fails with -# undefined gtest symbols — so it lives here once instead of being repeated per test. -# -# This also replaces `add_test(NAME x COMMAND x)`. Naming the binary as a single CTest entry -# hides which case failed and makes `ctest -R Suite.Case` impossible; discovery exposes each -# TEST() as its own CTest entry instead. -# -# Labels: exactly one per test, because gtest_discover_tests() cannot carry more than one. -# GoogleTestAddTests.cmake accumulates the generated script with `foreach(arg ${ARGN})`, so a -# label list serialises as bare `LABELS ci server` in the CTestTestfile, and CTest reads only -# the first value of a property and silently discards the rest. A label therefore classifies a -# test into exactly one group, and the three groups cover every selection worth making: -# -# ci the default hermetic set, so `ctest -LE bench` runs everything but benchmarks -# server servatrice-facing tests, so `ctest -L server` works -# bench the slow suites, kept out of the default run -# -# A second label is a configure-time FATAL_ERROR rather than a line CTest quietly reads as one. -# Anything orthogonal to a test's kind belongs in a helper flag beside the label, so it -# composes with the group instead of competing for the single slot. NO_SANITIZE is such a flag. -# -# TIMEOUT is bounded per test because CTest's implicit default is 1500s: one hung socket wait -# would otherwise stall CI for 25 minutes. -# -# EXTRA_LIBS is appended after the shared libraries so a caller adding a static archive (zlib, -# lzma) keeps it last, which is where the linker needs it. - -function(cockatrice_add_test) - cmake_parse_arguments( - PARSE_ARGV 0 ARG "MANUAL;NO_SANITIZE" "NAME" "SOURCES;LIBS;LABELS;DEFINITIONS;INCLUDE_DIRS;EXTRA_LIBS" - ) - - if(NOT ARG_NAME OR NOT ARG_SOURCES) - message(FATAL_ERROR "cockatrice_add_test requires at least NAME and SOURCES") - endif() - - add_executable(${ARG_NAME} ${ARG_SOURCES}) - - if(ARG_INCLUDE_DIRS) - target_include_directories(${ARG_NAME} PRIVATE ${ARG_INCLUDE_DIRS}) - endif() - - if(ARG_DEFINITIONS) - target_compile_definitions(${ARG_NAME} PRIVATE ${ARG_DEFINITIONS}) - endif() - +target_link_libraries(dummy_test Threads::Threads ${GTEST_BOTH_LIBRARIES}) +target_link_libraries(expression_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}) +target_link_libraries( + clamped_arithmetic_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} +) +target_link_libraries( + test_age_formatting libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} +) +target_link_libraries( + password_hash_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} +) +target_link_libraries( + playmat_resolver_test libcockatrice_deck_list libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} + ${TEST_QT_MODULES} +) +target_link_libraries( + deck_hash_performance_test libcockatrice_deck_list libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} + ${TEST_QT_MODULES} +) +target_link_libraries( + server_card_counter_test libcockatrice_network Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} +) +target_link_libraries( + server_counter_test libcockatrice_network Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} +) +target_link_libraries( + server_rate_limiter_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} +) +target_link_libraries( + server_developer_role_test libcockatrice_network libcockatrice_rng Threads::Threads ${GTEST_BOTH_LIBRARIES} + ${TEST_QT_MODULES} +) +target_link_libraries( + server_game_join_test libcockatrice_network_server_remote libcockatrice_rng Threads::Threads ${GTEST_BOTH_LIBRARIES} + ${TEST_QT_MODULES} +) +target_link_libraries( + warning_categories_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} +) +if(TARGET translation_loader_test) target_link_libraries( - ${ARG_NAME} ${ARG_LIBS} Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} ${ARG_EXTRA_LIBS} - ) - - # Only needed when gtest was downloaded and add_subdirectory'd above. An imported gtest - # target already carries its own dependency, so this must stay conditional. - if(NOT GTEST_FOUND) - add_dependencies(${ARG_NAME} gtest gtest_main) - endif() - - # MANUAL targets are built but deliberately kept out of ctest: benchmarks that need a card - # database, hit the network, or measure RAM. Leaving them unregistered keeps CI hermetic - # and fast. They still link gtest so they can use TEST() without a second code path. - if(ARG_MANUAL) - return() - endif() - - # NO_SANITIZE marks a target the sanitizer leg cannot run. Excluded by not handing its cases - # to CTest at all, rather than by a label, so the exclusion cannot rot into a line CTest - # quietly reads as something else. The target still builds, and still runs on the other legs. - if(ARG_NO_SANITIZE AND COCKATRICE_SANITIZERS) - return() - endif() - - # One label only, per the note above. Rejecting a second loudly beats emitting a - # CTestTestfile line that CTest will quietly read as one label. - list(LENGTH ARG_LABELS _label_count) - if(_label_count GREATER 1) - message(FATAL_ERROR "${ARG_NAME}: pass at most one label, got '${ARG_LABELS}'") - endif() - if(NOT ARG_LABELS) - set(ARG_LABELS ci) - endif() - - # GoogleTestAddTests defaults DISCOVERY_TIMEOUT to 5s, which is ample for a plain debug - # binary (--gtest_list_tests costs 0.04s) but not for an instrumented one (~2.8s cold, and - # discovery runs several binaries concurrently behind a -j build). Left at the default, a - # sanitizer build fails at the *link* step with a bare "Process terminated due to timeout" - # and no hint that discovery, not the test, is what is slow. - if(COCKATRICE_SANITIZERS) - set(_discovery_timeout 300) - else() - set(_discovery_timeout 60) - endif() - - gtest_discover_tests( - ${ARG_NAME} DISCOVERY_MODE POST_BUILD DISCOVERY_TIMEOUT ${_discovery_timeout} - PROPERTIES LABELS "${ARG_LABELS}" - TIMEOUT 60 - ) -endfunction() - -# Same wiring for targets that are built but deliberately kept out of ctest: benchmarks that -# need a card database, hit the network, or measure RAM. A separate name rather than a MANUAL -# flag spelled out at each call site, so "this is not in the CI suite" is visible in the CMake -# file. Note the unquoted expansions: an unset multi-value keyword expands to zero arguments, -# which is what lets this forward a caller's keywords verbatim without tripping the FATAL_ERROR -# guard above on an empty list. -function(cockatrice_add_manual_test) - cmake_parse_arguments(PARSE_ARGV 0 ARG "" "NAME" "SOURCES;LIBS;DEFINITIONS;INCLUDE_DIRS;EXTRA_LIBS") - - cockatrice_add_test( - NAME - ${ARG_NAME} - SOURCES - ${ARG_SOURCES} - LIBS - ${ARG_LIBS} - DEFINITIONS - ${ARG_DEFINITIONS} - INCLUDE_DIRS - ${ARG_INCLUDE_DIRS} - EXTRA_LIBS - ${ARG_EXTRA_LIBS} - MANUAL - ) -endfunction() - -# --- Unit tests ------------------------------------------------------------------ - -cockatrice_add_test(NAME expression_test SOURCES expression_test.cpp LIBS libcockatrice_utility) -cockatrice_add_test(NAME clamped_arithmetic_test SOURCES clamped_arithmetic_test.cpp LIBS libcockatrice_utility) -cockatrice_add_test(NAME test_age_formatting SOURCES test_age_formatting.cpp LIBS libcockatrice_utility) -cockatrice_add_test(NAME password_hash_test SOURCES password_hash_test.cpp LIBS libcockatrice_utility) -cockatrice_add_test(NAME report_categories_test SOURCES report_categories_test.cpp LIBS libcockatrice_utility) -cockatrice_add_test(NAME warning_categories_test SOURCES warning_categories_test.cpp LIBS libcockatrice_utility) -cockatrice_add_test( - NAME - playmat_resolver_test - SOURCES - playmat_resolver_test.cpp - LIBS - libcockatrice_deck_list - libcockatrice_utility -) -cockatrice_add_test( - NAME - server_rate_limiter_test - SOURCES - server_rate_limiter_test.cpp - LIBS - libcockatrice_utility - LABELS - server -) -cockatrice_add_test( - NAME - server_card_counter_test - SOURCES - server_card_counter_test.cpp - LIBS - libcockatrice_network - LABELS - server -) -cockatrice_add_test( - NAME - server_counter_test - SOURCES - server_counter_test.cpp - LIBS - libcockatrice_network - LABELS - server -) -cockatrice_add_test( - NAME - server_developer_role_test - NO_SANITIZE - SOURCES - server_developer_role_test.cpp - LIBS - libcockatrice_network - libcockatrice_rng - LABELS - server -) -cockatrice_add_test( - NAME - server_moderator_role_test - NO_SANITIZE - SOURCES - server_moderator_role_test.cpp - LIBS - libcockatrice_network - libcockatrice_rng - LABELS - server -) -cockatrice_add_test( - NAME - server_game_join_test - SOURCES - server_game_join_test.cpp - LIBS - libcockatrice_network_server_remote - libcockatrice_rng - LABELS - server -) -cockatrice_add_test( - NAME - latency_tracker_test - SOURCES - latency_tracker_test.cpp - LIBS - libcockatrice_network - LABELS - server -) -cockatrice_add_test( - NAME - lag_monitor_test - SOURCES - ${CMAKE_SOURCE_DIR}/cockatrice/src/client/lag_monitor.cpp - lag_monitor_test.cpp - INCLUDE_DIRS - ${CMAKE_SOURCE_DIR}/cockatrice/src -) -cockatrice_add_test( - NAME - metrics_registry_test - SOURCES - ${CMAKE_SOURCE_DIR}/servatrice/src/metrics_registry.cpp - metrics_registry_test.cpp - INCLUDE_DIRS - ${CMAKE_SOURCE_DIR}/servatrice/src - LABELS - server -) -cockatrice_add_test( - NAME - card_update_progress_test - SOURCES - ${CMAKE_SOURCE_DIR}/cockatrice/src/interface/card_database_update/card_update_progress.cpp - card_update_progress_test.cpp - INCLUDE_DIRS - ${CMAKE_SOURCE_DIR}/cockatrice/src -) -cockatrice_add_test( - NAME - loader_local_matching_test - SOURCES - ${CMAKE_SOURCE_DIR}/cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp - ${CMAKE_SOURCE_DIR}/cockatrice/src/client/settings/cache_settings.cpp - ${CMAKE_SOURCE_DIR}/cockatrice/src/client/settings/card_counter_settings.cpp - ${CMAKE_SOURCE_DIR}/cockatrice/src/client/settings/shortcuts_settings.cpp - ${CMAKE_SOURCE_DIR}/cockatrice/src/client/network/update/client/release_channel.cpp - ${VERSION_STRING_CPP} - loader_local_matching_test.cpp - LIBS - libcockatrice_settings - INCLUDE_DIRS - ${CMAKE_SOURCE_DIR}/cockatrice/src -) -cockatrice_add_test( - NAME - deck_hash_performance_test - SOURCES - deck_hash_performance_test.cpp - LIBS - libcockatrice_deck_list - libcockatrice_utility - LABELS - bench -) - -# The translation loader test compiles a .qm fixture, which needs lrelease from Qt Linguist. -if(TARGET Qt6::lrelease) - set(TRANSLATION_LOADER_TEST_QM "${CMAKE_CURRENT_BINARY_DIR}/translation_loader_test_de.qm") - add_custom_command( - OUTPUT "${TRANSLATION_LOADER_TEST_QM}" - COMMAND Qt6::lrelease "${CMAKE_CURRENT_SOURCE_DIR}/translation_loader_test.ts" -qm "${TRANSLATION_LOADER_TEST_QM}" - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/translation_loader_test.ts" - COMMENT "Compiling translation_loader_test.ts" - ) - add_custom_target(translation_loader_test_qm DEPENDS "${TRANSLATION_LOADER_TEST_QM}") - - cockatrice_add_test( - NAME - translation_loader_test - SOURCES - translation_loader_test.cpp - LIBS - libcockatrice_utility - DEFINITIONS - TRANSLATION_LOADER_TEST_DIR="${CMAKE_CURRENT_BINARY_DIR}" - ) - add_dependencies(translation_loader_test translation_loader_test_qm) - - # A multi-config generator (MSBuild, Xcode) puts the executable in a per-config subdirectory while - # the .qm stays in the current binary directory, so copy the fixture next to the binary: that is the - # layout ABuildTreeFindsTheTranslationsNextToTheExecutable is about, and the only one it finds on - # those generators. - add_custom_command( - TARGET translation_loader_test - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${TRANSLATION_LOADER_TEST_QM}" - "$/" - VERBATIM + translation_loader_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} ) endif() +target_link_libraries( + report_categories_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} +) +target_link_libraries(lag_monitor_test Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}) +target_link_libraries( + latency_tracker_test libcockatrice_network Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} +) +target_include_directories(metrics_registry_test PRIVATE ${CMAKE_SOURCE_DIR}/servatrice/src) +target_link_libraries(metrics_registry_test ${TEST_QT_MODULES} Threads::Threads ${GTEST_BOTH_LIBRARIES}) +target_link_libraries(card_update_progress_test Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}) +target_link_libraries( + loader_local_matching_test libcockatrice_settings Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} +) -# Guard must match the condition for src/single_instance_manager.cpp in cockatrice/CMakeLists.txt. if(WITH_CLIENT) - cockatrice_add_test( - NAME - single_instance_manager_test - SOURCES - ${CMAKE_SOURCE_DIR}/cockatrice/src/single_instance_manager.cpp - single_instance_manager_test.cpp - INCLUDE_DIRS - ${CMAKE_SOURCE_DIR}/cockatrice/src - ${CMAKE_SOURCE_DIR}/libcockatrice_utility - ) + target_link_libraries(single_instance_manager_test Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}) endif() add_subdirectory(card_zone_algorithms) diff --git a/tests/card_zone_algorithms/CMakeLists.txt b/tests/card_zone_algorithms/CMakeLists.txt index 945f8a0ed..e03d8f77d 100644 --- a/tests/card_zone_algorithms/CMakeLists.txt +++ b/tests/card_zone_algorithms/CMakeLists.txt @@ -1,4 +1,15 @@ -cockatrice_add_test( - NAME card_zone_algorithms_test SOURCES card_zone_algorithms_test.cpp INCLUDE_DIRS - ${CMAKE_SOURCE_DIR}/cockatrice/src/game/zones +add_executable(card_zone_algorithms_test card_zone_algorithms_test.cpp) + +target_include_directories(card_zone_algorithms_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src/game/zones) + +target_link_libraries( + card_zone_algorithms_test + PRIVATE Threads::Threads + PRIVATE ${GTEST_BOTH_LIBRARIES} ) + +add_test(NAME card_zone_algorithms_test COMMAND card_zone_algorithms_test) + +if(NOT GTEST_FOUND) + add_dependencies(card_zone_algorithms_test gtest) +endif() diff --git a/tests/carddatabase/CMakeLists.txt b/tests/carddatabase/CMakeLists.txt index 22171a9e8..811cebc1d 100644 --- a/tests/carddatabase/CMakeLists.txt +++ b/tests/carddatabase/CMakeLists.txt @@ -9,46 +9,61 @@ add_definitions("-DCARDDB_DATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/data/\"") # ------------------------ # Card Database Test # ------------------------ -cockatrice_add_test( - NAME +add_executable(carddatabase_test ${MOCKS_SOURCES} ${VERSION_STRING_CPP} carddatabase_test.cpp mocks.cpp) + +target_link_libraries( carddatabase_test - SOURCES - ${VERSION_STRING_CPP} - carddatabase_test.cpp - mocks.cpp - LIBS - libcockatrice_card + PRIVATE libcockatrice_card + PRIVATE Threads::Threads + PRIVATE ${GTEST_BOTH_LIBRARIES} + PRIVATE ${TEST_QT_MODULES} ) +add_test(NAME carddatabase_test COMMAND carddatabase_test) + +# ------------------------ +# Load Benchmark (manual, not run in CI) +# ------------------------ +add_executable(load_benchmark ${MOCKS_SOURCES} ${VERSION_STRING_CPP} load_benchmark.cpp mocks.cpp) + +target_link_libraries( + load_benchmark + PRIVATE libcockatrice_card + PRIVATE libcockatrice_models + PRIVATE Threads::Threads + PRIVATE ${GTEST_BOTH_LIBRARIES} + PRIVATE ${TEST_QT_MODULES} +) + +if(NOT GTEST_FOUND) + add_dependencies(load_benchmark gtest) +endif() + # ------------------------ # Filter String Test # (guard must match the condition for libcockatrice_filters in the root CMakeLists.txt) # ------------------------ if(WITH_ORACLE OR WITH_CLIENT) - cockatrice_add_test( - NAME + add_executable(filter_string_test ${MOCKS_SOURCES} ${VERSION_STRING_CPP} filter_string_test.cpp mocks.cpp) + + target_link_libraries( filter_string_test - SOURCES - ${VERSION_STRING_CPP} - filter_string_test.cpp - mocks.cpp - LIBS - libcockatrice_filters + PRIVATE libcockatrice_filters + PRIVATE Threads::Threads + PRIVATE ${GTEST_BOTH_LIBRARIES} + PRIVATE ${TEST_QT_MODULES} ) + + add_test(NAME filter_string_test COMMAND filter_string_test) + + if(NOT GTEST_FOUND) + add_dependencies(filter_string_test gtest) + endif() endif() # ------------------------ -# Load Benchmark (manual, not run in CI) +# Dependencies on gtest # ------------------------ -cockatrice_add_manual_test( - NAME - load_benchmark - SOURCES - ${VERSION_STRING_CPP} - load_benchmark.cpp - mocks.cpp - LIBS - libcockatrice_card - libcockatrice_models - MANUAL -) +if(NOT GTEST_FOUND) + add_dependencies(carddatabase_test gtest) +endif() diff --git a/tests/deck_list_model/CMakeLists.txt b/tests/deck_list_model/CMakeLists.txt index cd7d693f3..e3096c559 100644 --- a/tests/deck_list_model/CMakeLists.txt +++ b/tests/deck_list_model/CMakeLists.txt @@ -1,23 +1,33 @@ -cockatrice_add_test( - NAME - deck_list_model_custom_zones_test - SOURCES - ${VERSION_STRING_CPP} - deck_list_model_custom_zones_test.cpp - LIBS - libcockatrice_models - libcockatrice_card - libcockatrice_deck_list -) +add_executable(deck_list_model_custom_zones_test ${VERSION_STRING_CPP} deck_list_model_custom_zones_test.cpp) -cockatrice_add_test( - NAME - deck_list_model_zone_integration_test - SOURCES - ${VERSION_STRING_CPP} - deck_list_model_zone_integration_test.cpp - LIBS +if(NOT GTEST_FOUND) + add_dependencies(deck_list_model_custom_zones_test gtest) +endif() + +target_link_libraries( + deck_list_model_custom_zones_test libcockatrice_models libcockatrice_card libcockatrice_deck_list + Threads::Threads + ${GTEST_BOTH_LIBRARIES} + ${TEST_QT_MODULES} ) +add_test(NAME deck_list_model_custom_zones_test COMMAND deck_list_model_custom_zones_test) + +add_executable(deck_list_model_zone_integration_test ${VERSION_STRING_CPP} deck_list_model_zone_integration_test.cpp) + +if(NOT GTEST_FOUND) + add_dependencies(deck_list_model_zone_integration_test gtest) +endif() + +target_link_libraries( + deck_list_model_zone_integration_test + libcockatrice_models + libcockatrice_card + libcockatrice_deck_list + Threads::Threads + ${GTEST_BOTH_LIBRARIES} + ${TEST_QT_MODULES} +) +add_test(NAME deck_list_model_zone_integration_test COMMAND deck_list_model_zone_integration_test) diff --git a/tests/deck_list_zones/CMakeLists.txt b/tests/deck_list_zones/CMakeLists.txt index 7af8fd187..0710be94d 100644 --- a/tests/deck_list_zones/CMakeLists.txt +++ b/tests/deck_list_zones/CMakeLists.txt @@ -1 +1,10 @@ -cockatrice_add_test(NAME deck_list_zones_test SOURCES deck_list_zones_test.cpp LIBS libcockatrice_deck_list) +add_executable(deck_list_zones_test deck_list_zones_test.cpp) + +if(NOT GTEST_FOUND) + add_dependencies(deck_list_zones_test gtest) +endif() + +target_link_libraries( + deck_list_zones_test libcockatrice_deck_list Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} +) +add_test(NAME deck_list_zones_test COMMAND deck_list_zones_test) diff --git a/tests/dummy_test.cpp b/tests/dummy_test.cpp new file mode 100644 index 000000000..8fd9c78dd --- /dev/null +++ b/tests/dummy_test.cpp @@ -0,0 +1,20 @@ +#include "gtest/gtest.h" +#include + +namespace +{ +class FooTest : public ::testing::Test +{ +}; + +TEST(DummyTest, Works) +{ + ASSERT_EQ(1, 1) << "One is not equal to one"; +} +} // namespace + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/tests/loading_from_clipboard/CMakeLists.txt b/tests/loading_from_clipboard/CMakeLists.txt index 88711e457..719d62f45 100644 --- a/tests/loading_from_clipboard/CMakeLists.txt +++ b/tests/loading_from_clipboard/CMakeLists.txt @@ -1,13 +1,12 @@ add_definitions("-DCARDDB_DATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/data/\"") +add_executable(loading_from_clipboard_test ${VERSION_STRING_CPP} clipboard_testing.cpp loading_from_clipboard_test.cpp) -cockatrice_add_test( - NAME - loading_from_clipboard_test - SOURCES - ${VERSION_STRING_CPP} - clipboard_testing.cpp - loading_from_clipboard_test.cpp - LIBS - libcockatrice_deck_list - libcockatrice_card +if(NOT GTEST_FOUND) + add_dependencies(loading_from_clipboard_test gtest) +endif() + +target_link_libraries( + loading_from_clipboard_test libcockatrice_deck_list libcockatrice_card Threads::Threads ${GTEST_BOTH_LIBRARIES} + ${TEST_QT_MODULES} ) +add_test(NAME loading_from_clipboard_test COMMAND loading_from_clipboard_test) diff --git a/tests/movecard_tests/CMakeLists.txt b/tests/movecard_tests/CMakeLists.txt index 68d8fc84c..769047148 100755 --- a/tests/movecard_tests/CMakeLists.txt +++ b/tests/movecard_tests/CMakeLists.txt @@ -1,11 +1,16 @@ -cockatrice_add_test( - NAME +add_executable(reverse_card_move_test reverse_card_move_test.cpp) + +if(NOT GTEST_FOUND) + add_dependencies(reverse_card_move_test gtest) +endif() + +target_link_libraries( reverse_card_move_test - SOURCES - reverse_card_move_test.cpp - LIBS - libcockatrice_network_server_remote - libcockatrice_rng - LABELS - server + PRIVATE libcockatrice_network_server_remote + PRIVATE libcockatrice_rng + PRIVATE Threads::Threads + PRIVATE ${GTEST_BOTH_LIBRARIES} + PRIVATE ${TEST_QT_MODULES} ) + +add_test(NAME reverse_card_move_test COMMAND reverse_card_move_test) diff --git a/tests/oracle/CMakeLists.txt b/tests/oracle/CMakeLists.txt index 6958673f7..9bc5ee5be 100644 --- a/tests/oracle/CMakeLists.txt +++ b/tests/oracle/CMakeLists.txt @@ -1,32 +1,37 @@ -cockatrice_add_test(NAME parse_cipt_test SOURCES ../../oracle/src/parsehelpers.cpp parse_cipt_test.cpp) +add_executable(parse_cipt_test ../../oracle/src/parsehelpers.cpp parse_cipt_test.cpp) + +if(NOT GTEST_FOUND) + add_dependencies(parse_cipt_test gtest) +endif() + +target_link_libraries(parse_cipt_test Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}) + +add_test(NAME parse_cipt_test COMMAND parse_cipt_test) # Oracle importer unit tests -cockatrice_add_test( - NAME - oracle_importer_test - SOURCES - ${VERSION_STRING_CPP} - ../../oracle/src/oracleimporter.cpp - ../../oracle/src/parsehelpers.cpp - ../../oracle/src/raw_json_scanner.cpp - oracle_importer_test.cpp - LIBS - libcockatrice_card - libcockatrice_interfaces +add_executable( + oracle_importer_test ${VERSION_STRING_CPP} ../../oracle/src/oracleimporter.cpp ../../oracle/src/parsehelpers.cpp + ../../oracle/src/raw_json_scanner.cpp oracle_importer_test.cpp ) +if(NOT GTEST_FOUND) + add_dependencies(oracle_importer_test gtest) +endif() + +target_link_libraries( + oracle_importer_test libcockatrice_card libcockatrice_interfaces Threads::Threads ${GTEST_BOTH_LIBRARIES} + ${TEST_QT_MODULES} +) + +add_test(NAME oracle_importer_test COMMAND oracle_importer_test) + # Oracle importer benchmark tests (manual, not run in CI, incl. RAM benchmark) # Optional compression libs, mirrored from oracle/CMakeLists.txt, so the benchmark # can download and decompress whatever AllPrintings format the default URL selects. -# add_definitions() is directory-scoped and order-independent, so HAS_ZLIB/HAS_LZMA also -# reach the two unit tests above. That is harmless: those macros gate code only in -# oracle/src/pages.cpp and oracle/src/pagetemplates.cpp, which no test target compiles. find_package(ZLIB) if(ZLIB_FOUND) add_definitions("-DHAS_ZLIB") set(_ORACLE_BENCH_EXTRA_SOURCES ../../oracle/src/zip/unzip.cpp ../../oracle/src/zip/zipglobal.cpp) - cockatrice_disable_sanitizers_for(${_ORACLE_BENCH_EXTRA_SOURCES}) - cockatrice_disable_coverage_for(${_ORACLE_BENCH_EXTRA_SOURCES}) set(_ORACLE_BENCH_EXTRA_LIBRARIES ${ZLIB_LIBRARIES}) include_directories(${ZLIB_INCLUDE_DIRS}) else() @@ -37,28 +42,28 @@ find_package(LibLZMA) if(LIBLZMA_FOUND) add_definitions("-DHAS_LZMA") list(APPEND _ORACLE_BENCH_EXTRA_SOURCES ../../oracle/src/lzma/decompress.cpp) - cockatrice_disable_sanitizers_for(../../oracle/src/lzma/decompress.cpp) - cockatrice_disable_coverage_for(../../oracle/src/lzma/decompress.cpp) list(APPEND _ORACLE_BENCH_EXTRA_LIBRARIES ${LIBLZMA_LIBRARIES}) include_directories(${LIBLZMA_INCLUDE_DIRS}) else() message(STATUS "Oracle tests: LibLZMA not found; xz download benchmark disabled") endif() -cockatrice_add_manual_test( - NAME +add_executable( + oracle_importer_benchmark_test + ${VERSION_STRING_CPP} ../../oracle/src/oracleimporter.cpp ../../oracle/src/parsehelpers.cpp + ../../oracle/src/raw_json_scanner.cpp oracle_importer_benchmark_test.cpp ${_ORACLE_BENCH_EXTRA_SOURCES} +) + +if(NOT GTEST_FOUND) + add_dependencies(oracle_importer_benchmark_test gtest) +endif() + +target_link_libraries( oracle_importer_benchmark_test - SOURCES - ${VERSION_STRING_CPP} - ../../oracle/src/oracleimporter.cpp - ../../oracle/src/parsehelpers.cpp - ../../oracle/src/raw_json_scanner.cpp - oracle_importer_benchmark_test.cpp - ${_ORACLE_BENCH_EXTRA_SOURCES} - LIBS libcockatrice_card libcockatrice_interfaces - EXTRA_LIBS + Threads::Threads + ${GTEST_BOTH_LIBRARIES} + ${TEST_QT_MODULES} ${_ORACLE_BENCH_EXTRA_LIBRARIES} - MANUAL ) diff --git a/tests/picture_loader_benchmark/CMakeLists.txt b/tests/picture_loader_benchmark/CMakeLists.txt index 0684f58f4..a3b75fe88 100644 --- a/tests/picture_loader_benchmark/CMakeLists.txt +++ b/tests/picture_loader_benchmark/CMakeLists.txt @@ -1,10 +1,8 @@ -# Manual benchmark hitting the real card image hosts. Not registered with CTest: it requires a -# cards.xml, touches the network for minutes at a time, and needs network access. Build it -# explicitly and run by hand. -cockatrice_add_manual_test( - NAME +# Manual benchmark hitting the real card image hosts. Not registered with +# add_test(): it requires a cards.xml, touches the network for minutes at a +# time, and needs network access. Build it explicitly and run by hand. +add_executable( picture_loader_benchmark_test - SOURCES ${VERSION_STRING_CPP} ../../cockatrice/src/interface/card_picture_loader/card_picture_loader_local.cpp ../../cockatrice/src/interface/card_picture_loader/card_picture_to_load.cpp @@ -13,12 +11,16 @@ cockatrice_add_manual_test( ../../cockatrice/src/client/settings/cache_settings.h picture_loader_benchmark.cpp settings_cache_mock.cpp - LIBS +) + +target_include_directories(picture_loader_benchmark_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src) + +target_link_libraries( + picture_loader_benchmark_test libcockatrice_card libcockatrice_settings libcockatrice_interfaces libcockatrice_utility - INCLUDE_DIRS - ${CMAKE_SOURCE_DIR}/cockatrice/src - MANUAL + Threads::Threads + ${TEST_QT_MODULES} ) diff --git a/tests/server_moderator_role_test.cpp b/tests/server_moderator_role_test.cpp deleted file mode 100644 index 2517a2d86..000000000 --- a/tests/server_moderator_role_test.cpp +++ /dev/null @@ -1,156 +0,0 @@ -/** @file server_moderator_role_test.cpp - * @brief Tests for the moderator staff role authorization and dispatch. - * @ingroup Tests - */ - -#include "libcockatrice/protocol/pb/response.pb.h" -#include "libcockatrice/protocol/pb/server_message.pb.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// The server_remote library references the global RNG, which is normally -// defined by the servatrice/client executable main(). Provide a stub so the -// unit test can link against it. -RNG_Abstract *rng = nullptr; - -namespace -{ - -class TestModeratorHandler : public Server_ProtocolHandler -{ -public: - explicit TestModeratorHandler(Server *_server) : Server_ProtocolHandler(_server, nullptr) - { - } - - QString getAddress() const override - { - return {}; - } - QString getConnectionType() const override - { - return {}; - } - - // Buffer the last response code sent to the client so tests can assert on - // the outcome of processCommandContainer(). - Response::ResponseCode lastResponseCode = Response::RespNothing; - int dispatchCount = 0; - -protected: - void transmitProtocolItem(const ServerMessage &item) override - { - if (item.message_type() == ServerMessage::RESPONSE) { - lastResponseCode = item.response().response_code(); - } - } - - Response::ResponseCode - processExtendedModeratorCommand(int cmdType, const ModeratorCommand &, ResponseContainer &) override - { - ++dispatchCount; - // Fail closed for anything not explicitly handled. - if (cmdType != ModeratorCommand::REPORT_REOPEN) { - return Response::RespFunctionNotAllowed; - } - return Response::RespOk; - } -}; - -class ModeratorRoleTest : public ::testing::Test -{ -protected: - Server server; - TestModeratorHandler handler{&server}; - - void setUserLevel(uint32_t level) - { - ServerInfo_User user; - user.set_user_level(level); - handler.setUserInfo(user); - } -}; - -TEST_F(ModeratorRoleTest, RejectsWhenNotLoggedIn) -{ - CommandContainer cont; - cont.add_moderator_command(); - handler.processCommandContainer(cont); - EXPECT_EQ(handler.lastResponseCode, Response::RespLoginNeeded); - EXPECT_EQ(handler.dispatchCount, 0); -} - -TEST_F(ModeratorRoleTest, RejectsPlainUser) -{ - setUserLevel(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered); - - CommandContainer cont; - cont.add_moderator_command(); - handler.processCommandContainer(cont); - EXPECT_EQ(handler.lastResponseCode, Response::RespLoginNeeded); - EXPECT_EQ(handler.dispatchCount, 0); -} - -TEST_F(ModeratorRoleTest, DispatchesToModeratorCommandForModerator) -{ - setUserLevel(ServerInfo_User::IsModerator); - - CommandContainer cont; - ModeratorCommand *cmd = cont.add_moderator_command(); - cmd->MutableExtension(Command_ReportReopen::ext); - handler.processCommandContainer(cont); - EXPECT_EQ(handler.lastResponseCode, Response::RespOk); - EXPECT_EQ(handler.dispatchCount, 1); -} - -TEST_F(ModeratorRoleTest, DispatchesToModeratorCommandForAdmin) -{ - setUserLevel(ServerInfo_User::IsModerator | ServerInfo_User::IsAdmin); - - CommandContainer cont; - ModeratorCommand *cmd = cont.add_moderator_command(); - cmd->MutableExtension(Command_ReportReopen::ext); - handler.processCommandContainer(cont); - EXPECT_EQ(handler.lastResponseCode, Response::RespOk); - EXPECT_EQ(handler.dispatchCount, 1); -} - -TEST_F(ModeratorRoleTest, RejectsDeveloperThatIsNotModerator) -{ - setUserLevel(ServerInfo_User::IsDeveloper); - - CommandContainer cont; - cont.add_moderator_command(); - handler.processCommandContainer(cont); - EXPECT_EQ(handler.lastResponseCode, Response::RespLoginNeeded); - EXPECT_EQ(handler.dispatchCount, 0); -} - -TEST_F(ModeratorRoleTest, FailClosedForUnknownModeratorCommand) -{ - setUserLevel(ServerInfo_User::IsModerator); - - CommandContainer cont; - cont.add_moderator_command(); // no extension set -> getPbExtension() returns -1 - handler.processCommandContainer(cont); - EXPECT_EQ(handler.lastResponseCode, Response::RespFunctionNotAllowed); - EXPECT_EQ(handler.dispatchCount, 1); -} - -} // namespace - -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/tests/settings/CMakeLists.txt b/tests/settings/CMakeLists.txt index 98bed1f4b..7ee32a645 100644 --- a/tests/settings/CMakeLists.txt +++ b/tests/settings/CMakeLists.txt @@ -1,14 +1,26 @@ -cockatrice_add_test(NAME settings_manager_test SOURCES settings_manager_test.cpp LIBS libcockatrice_settings) +add_executable(settings_manager_test settings_manager_test.cpp) +add_executable(settings_defaults_test settings_defaults_test.cpp) +add_executable(settings_migration_test settings_migration_test.cpp) -cockatrice_add_test( - NAME - settings_defaults_test - SOURCES - settings_defaults_test.cpp - LIBS - libcockatrice_settings - DEFINITIONS - SETTINGS_UNIT_TEST +target_link_libraries( + settings_manager_test libcockatrice_settings Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} ) -cockatrice_add_test(NAME settings_migration_test SOURCES settings_migration_test.cpp LIBS libcockatrice_settings) +target_link_libraries( + settings_defaults_test libcockatrice_settings Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} +) +target_compile_definitions(settings_defaults_test PRIVATE SETTINGS_UNIT_TEST) + +target_link_libraries( + settings_migration_test libcockatrice_settings Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES} +) + +add_test(NAME settings_manager_test COMMAND settings_manager_test) +add_test(NAME settings_defaults_test COMMAND settings_defaults_test) +add_test(NAME settings_migration_test COMMAND settings_migration_test) + +if(NOT GTEST_FOUND) + add_dependencies(settings_manager_test gtest) + add_dependencies(settings_defaults_test gtest) + add_dependencies(settings_migration_test gtest) +endif() diff --git a/tests/settings/settings_defaults_test.cpp b/tests/settings/settings_defaults_test.cpp index 2ba9f3fbb..65e41af76 100644 --- a/tests/settings/settings_defaults_test.cpp +++ b/tests/settings/settings_defaults_test.cpp @@ -1,7 +1,6 @@ #include "libcockatrice/card/card_localization.h" #include "gtest/gtest.h" -#include #include #include #include @@ -16,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -697,55 +695,6 @@ TEST_F(SettingsDefaultsTest, VisualDeckStorage_ShowUploadTime_SetAndGet) ASSERT_EQ(s.getVisualDeckStorageShowUploadTime(), false); } -TEST_F(SettingsDefaultsTest, Paths_AppDirWarningAcknowledged_Default) -{ - PathsSettings s(settingsPath, nullptr); - ASSERT_EQ(s.getAppDirWarningAcknowledged(), false); -} - -TEST_F(SettingsDefaultsTest, Paths_AppDirWarningAcknowledged_SetAndGet) -{ - PathsSettings s(settingsPath, nullptr); - s.setAppDirWarningAcknowledged(true); - ASSERT_EQ(s.getAppDirWarningAcknowledged(), true); -} - -TEST_F(SettingsDefaultsTest, Paths_PathsInsideDir_DetectsNestedAndIgnoresSiblings) -{ - PathsSettings s(settingsPath, nullptr); - const QString base = QDir::cleanPath(settingsPath); - - s.setDeckPath(base + "/decks/"); - s.setPicsPath(base + "/pics"); - s.setReplaysPath(base + "2"); // sibling sharing the name prefix, not nested - - const QStringList offenders = s.pathsInsideDir(base); - ASSERT_EQ(offenders.size(), 2); - ASSERT_TRUE(offenders.contains(base + "/decks")); - ASSERT_TRUE(offenders.contains(base + "/pics")); -} - -TEST_F(SettingsDefaultsTest, Paths_PathsInsideDir_EmptyWhenNothingMatches) -{ - PathsSettings s(settingsPath, nullptr); - s.setDeckPath("/somewhere/else/decks"); - ASSERT_TRUE(s.pathsInsideDir(QDir::cleanPath(settingsPath)).isEmpty()); -} - -TEST_F(SettingsDefaultsTest, Paths_PathsInsideDir_MatchesExactBaseAndNormalizesDots) -{ - PathsSettings s(settingsPath, nullptr); - const QString base = QDir::cleanPath(settingsPath); - - s.setCustomPicsPath(base); // exactly the base directory - s.setThemesPath(base + "/pics/../decks"); // needs cleanPath normalization - - const QStringList offenders = s.pathsInsideDir(base); - ASSERT_EQ(offenders.size(), 2); - ASSERT_TRUE(offenders.contains(base)); - ASSERT_TRUE(offenders.contains(base + "/decks")); -} - } // namespace int main(int argc, char **argv) diff --git a/tests/theme_migration/CMakeLists.txt b/tests/theme_migration/CMakeLists.txt index 93493ee5f..ac202210e 100644 --- a/tests/theme_migration/CMakeLists.txt +++ b/tests/theme_migration/CMakeLists.txt @@ -1,7 +1,5 @@ -cockatrice_add_test( - NAME +add_executable( theme_migration_test - SOURCES ${CMAKE_SOURCE_DIR}/cockatrice/src/interface/theme_manager.cpp ${CMAKE_SOURCE_DIR}/cockatrice/src/interface/theme_config.cpp ${CMAKE_SOURCE_DIR}/cockatrice/src/interface/pixel_map_generator.cpp @@ -11,11 +9,22 @@ cockatrice_add_test( ${CMAKE_SOURCE_DIR}/cockatrice/src/client/network/update/client/release_channel.cpp ${VERSION_STRING_CPP} theme_migration_test.cpp - LIBS +) +target_include_directories(theme_migration_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src) + +target_link_libraries( + theme_migration_test libcockatrice_settings libcockatrice_network libcockatrice_network_server_remote libcockatrice_protocol - INCLUDE_DIRS - ${CMAKE_SOURCE_DIR}/cockatrice/src + Threads::Threads + ${GTEST_BOTH_LIBRARIES} + ${TEST_QT_MODULES} ) + +add_test(NAME theme_migration_test COMMAND theme_migration_test) + +if(NOT GTEST_FOUND) + add_dependencies(theme_migration_test gtest) +endif()