diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..69b1284 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,173 @@ +name: build + +on: + push: + branches: + - add-tests + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + windows: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + + - name: Install Catch2 + shell: bash + run: | + curl -L https://github.com/catchorg/Catch2/archive/v2.13.4.tar.gz -o catch2.tar.gz + tar xvf catch2.tar.gz + cd Catch2-2.13.4 + cmake -Bbuild -H. -DBUILD_TESTING=OFF + cmake --build build/ --target install + + - name: Fetch external libs + run: | + # QtApng + curl -L https://github.com/Skycoder42/QtApng/releases/download/1.1.4/qtapng-msvc2017_64-5.14.1.zip -o apng.zip + unzip apng.zip + + # discord-rpc + curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-win.zip -o discord_rpc_win.zip + unzip discord_rpc_win.zip + cp ./discord-rpc/win64-dynamic/lib/discord-rpc.lib ./lib + + # BASS + curl http://www.un4seen.com/files/bass24.zip -o bass.zip + unzip bass.zip + cp ./c/x64/bass.lib ./lib + + # BASS Opus + curl http://www.un4seen.com/files/bassopus24.zip -o bassopus.zip + unzip bassopus.zip + cp ./c/x64/bassopus.lib ./lib + + - name: Cache Qt + id: cache-qt + uses: actions/cache@v1 + with: + path: ../Qt + key: ${{ runner.os }}-qt5 + + - name: Install Qt + uses: jurplel/install-qt-action@v2 + with: + version: '5.15.2' + cached: ${{steps.cache-qt.outputs.cache-hit}} + + - name: Create Build Environment + run: cmake -E make_directory ${{github.workspace}}/build + + - name: Configure CMake + shell: bash + working-directory: ${{github.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE + + - name: Build + working-directory: ${{github.workspace}}/build + shell: bash + run: cmake --build . --config $BUILD_TYPE --target Attorney_Online + + - name: Deploy + working-directory: ${{github.workspace}}/build/Release + shell: bash + run: | + windeployqt . + cp ../../msvc2017_64/plugins/imageformats/qapng.dll ./imageformats/ + cp ../../discord-rpc/win64-dynamic/bin/discord-rpc.dll . + cp ../../x64/bass.dll . + cp ../../x64/bassopus.dll . + + - name: Upload Artifact + uses: actions/upload-artifact@v2 + with: + name: Attorney_Online-x64 + path: ${{github.workspace}}/build/Release/ + + # linux: + # # The CMake configure and build commands are platform agnostic and should work equally + # # well on Windows or Mac. You can convert this to a matrix build if you need + # # cross-platform coverage. + # # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + # runs-on: ubuntu-20.04 + + # steps: + # - uses: actions/checkout@v2 + + # - name: Install catch2 + # run: | + # curl -L https://github.com/catchorg/Catch2/archive/v2.13.4.tar.gz -o catch2.tar.gz + # tar xvf catch2.tar.gz + # cd Catch2-2.13.4 + # cmake -Bbuild -H. -DBUILD_TESTING=OFF + # sudo cmake --build build/ --target install + + # - name: Fetch external libs + # run: | + # # Download + # curl http://www.un4seen.com/files/bass24-linux.zip -o bass_linux.zip + # curl http://www.un4seen.com/files/bassopus24-linux.zip -o bassopus_linux.zip + # curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip -o discord_rpc_linux.zip + # # Extract + # unzip bass_linux.zip + # unzip bassopus_linux.zip + # unzip discord_rpc_linux.zip + # # Copy + # cp x64/libbass.so lib + # cp x64/libbassopus.so lib + # cp discord-rpc/linux-dynamic/lib/libdiscord-rpc.so lib + + # - name: Install Qt5 + # run: sudo apt update -y && sudo apt install -y qt5-default + + # - name: Install QtApng + # run: | + # git clone https://github.com/Skycoder42/QtApng + # cd QtApng + # qmake + # make + # sudo make install + + # - name: Create Build Environment + # # Some projects don't allow in-source building, so create a separate build directory + # # We'll use this as our working directory for all subsequent commands + # run: cmake -E make_directory ${{github.workspace}}/build + + # - name: Configure CMake + # # Use a bash shell so we can use the same syntax for environment variable + # # access regardless of the host operating system + # shell: bash + # env: + # CC: gcc-10 + # CXX: g++-10 + # working-directory: ${{github.workspace}}/build + # # Note the current convention is to use the -S and -B options here to specify source + # # and build directories, but this is only available with CMake 3.13 and higher. + # # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + # run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE + + # - name: Build + # working-directory: ${{github.workspace}}/build + # shell: bash + # # Execute the build. You can specify a specific target with "--target " + # run: cmake --build . --config $BUILD_TYPE --target Attorney_Online + + # - name: Strip + # working-directory: ${{github.workspace}}/build + # shell: bash + # run: strip -s Attorney_Online + + # - name: Compress + # working-directory: ${{github.workspace}}/build + # shell: bash + # run: tar czvf Attorney_Online-linux-x86_64.tgz Attorney_Online + + # - name: Upload Artifact + # uses: actions/upload-artifact@v2 + # with: + # name: Attorney_Online + # path: ${{github.workspace}}/build/Attorney_Online-linux-x86_64.tgz diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..4350a7c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,87 @@ +name: test + +on: + push: + branches: + - add-tests + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally + # well on Windows or Mac. You can convert this to a matrix build if you need + # cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + + - name: Install catch2 + run: | + curl -L https://github.com/catchorg/Catch2/archive/v2.13.4.tar.gz -o catch2.tar.gz + tar xvf catch2.tar.gz + cd Catch2-2.13.4 + cmake -Bbuild -H. -DBUILD_TESTING=OFF + sudo cmake --build build/ --target install + + - name: Fetch external libs + run: | + # Download + curl http://www.un4seen.com/files/bass24-linux.zip -o bass_linux.zip + curl http://www.un4seen.com/files/bassopus24-linux.zip -o bassopus_linux.zip + curl -L https://github.com/discordapp/discord-rpc/releases/download/v3.4.0/discord-rpc-linux.zip -o discord_rpc_linux.zip + # Extract + unzip bass_linux.zip + unzip bassopus_linux.zip + unzip discord_rpc_linux.zip + # Copy + cp x64/libbass.so lib + cp x64/libbassopus.so lib + cp discord-rpc/linux-dynamic/lib/libdiscord-rpc.so lib + + - name: Install Qt5 + run: sudo apt update -y && sudo apt install -y qt5-default + + - name: Install QtApng + run: | + git clone https://github.com/Skycoder42/QtApng + cd QtApng + qmake + make + sudo make install + + - name: Create Build Environment + # Some projects don't allow in-source building, so create a separate build directory + # We'll use this as our working directory for all subsequent commands + run: cmake -E make_directory ${{github.workspace}}/build + + - name: Configure CMake + # Use a bash shell so we can use the same syntax for environment variable + # access regardless of the host operating system + shell: bash + env: + CC: gcc-10 + CXX: g++-10 + working-directory: ${{github.workspace}}/build + # Note the current convention is to use the -S and -B options here to specify source + # and build directories, but this is only available with CMake 3.13 and higher. + # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE + + - name: Build + working-directory: ${{github.workspace}}/build + shell: bash + # Execute the build. You can specify a specific target with "--target " + run: cmake --build . --config $BUILD_TYPE --target test + + - name: Test + working-directory: ${{github.workspace}}/build/test + shell: bash + env: + QT_QPA_PLATFORM: offscreen + run: | + ln -s ../../test/*.png . + ./test ~[noci] diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b6b0210 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,43 @@ +# Configure cmake +cmake_minimum_required(VERSION 3.1.0) +cmake_policy(SET CMP0076 NEW) # silence warning + +project(AttorneyOnline) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +if(CMAKE_VERSION VERSION_LESS "3.7.0") + set(CMAKE_INCLUDE_CURRENT_DIR ON) +endif() + +# AO +add_executable(Attorney_Online resources.qrc) + +# WIN32 +if(WIN32) + if(CMAKE_BUILD_TYPE STREQUAL "Release") + set_property(TARGET Attorney_Online PROPERTY WIN32_EXECUTABLE true) + set(APP_ICON_RESOURCE_WINDOWS "${CMAKE_CURRENT_SOURCE_DIR}/resource/logo_ao2.rc") + target_sources(Attorney_Online PRIVATE ${APP_ICON_RESOURCE_WINDOWS}) + endif() +endif() + +# Target Include +target_include_directories(Attorney_Online PRIVATE include) + +# Target Lib +find_package(Qt5 COMPONENTS Core Gui Network Widgets REQUIRED) +target_link_directories(Attorney_Online PRIVATE lib) +target_link_libraries(Attorney_Online PRIVATE Qt5::Core Qt5::Gui Qt5::Network Qt5::Widgets + bass bassopus discord-rpc) +target_compile_definitions(Attorney_Online PRIVATE DISCORD) + +# Subdirectories +add_subdirectory(test) +add_subdirectory(src) +add_subdirectory(include) diff --git a/README_TEST.md b/README_TEST.md new file mode 100644 index 0000000..774ad3c --- /dev/null +++ b/README_TEST.md @@ -0,0 +1,17 @@ +Running tests requires Catch2 and cmake + +# Running Tests +```sh +mkdir cbuild && cd cbuild +cmake .. +make test + +# usage: run all tests +./test/test + +# usage: Optionally specify tests and success verbosity +./test/test [bass] --success +``` + +# Writing Tests +`[noci]` tag is used to disable a test on Github actions diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt new file mode 100644 index 0000000..18cf23b --- /dev/null +++ b/include/CMakeLists.txt @@ -0,0 +1,38 @@ +target_sources(Attorney_Online PRIVATE +aoapplication.h +aoblipplayer.h +aobutton.h +aocaseannouncerdialog.h +aocharbutton.h +aoclocklabel.h +aoemotebutton.h +aoevidencebutton.h +aoevidencedisplay.h +aoimage.h +aolayer.h +aolineedit.h +aomusicplayer.h +aooptionsdialog.h +aopacket.h +aosfxplayer.h +aotextarea.h +aotextedit.h +bass.h +bassopus.h +chatlogpiece.h +courtroom.h +datatypes.h +debug_functions.h +demoserver.h +discord-rpc.h +discord_register.h +discord_rich_presence.h +discord_rpc.h +file_functions.h +hardware_functions.h +lobby.h +misc_functions.h +networkmanager.h +scrolltext.h +text_file_functions.h +) diff --git a/resource/logo_ao2.rc b/resource/logo_ao2.rc new file mode 100644 index 0000000..ed785de --- /dev/null +++ b/resource/logo_ao2.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON "logo_ao2.ico" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..b04db8b --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,38 @@ +target_sources(Attorney_Online PRIVATE +aoapplication.cpp +aoblipplayer.cpp +aobutton.cpp +aocaseannouncerdialog.cpp +aocharbutton.cpp +aoclocklabel.cpp +aoemotebutton.cpp +aoevidencebutton.cpp +aoevidencedisplay.cpp +aoimage.cpp +aolayer.cpp +aolineedit.cpp +aomusicplayer.cpp +aooptionsdialog.cpp +aopacket.cpp +aosfxplayer.cpp +aotextarea.cpp +aotextedit.cpp +charselect.cpp +chatlogpiece.cpp +courtroom.cpp +debug_functions.cpp +demoserver.cpp +discord_rich_presence.cpp +emotes.cpp +evidence.cpp +file_functions.cpp +hardware_functions.cpp +lobby.cpp +main.cpp +misc_functions.cpp +networkmanager.cpp +packet_distribution.cpp +path_functions.cpp +scrolltext.cpp +text_file_functions.cpp +) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..e43b551 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,8 @@ +find_package(Qt5 COMPONENTS Core Gui Widgets REQUIRED) +find_package(Catch2 REQUIRED) + +add_executable(test test_aopacket.cpp test_caseloading.cpp test_apng.cpp test_bass.cpp ../include/aopacket.h ../src/aopacket.cpp) +target_include_directories(test PRIVATE ../include) +target_link_directories(test PRIVATE ../lib) +target_link_libraries(test PRIVATE Qt5::Core Qt5::Gui Qt5::Widgets Catch2::Catch2 bass bassopus discord-rpc) +target_compile_definitions(Attorney_Online PRIVATE DISCORD) diff --git a/test/missle.png b/test/missle.png new file mode 100644 index 0000000..6f373b4 Binary files /dev/null and b/test/missle.png differ diff --git a/test/snackoo.png b/test/snackoo.png new file mode 100644 index 0000000..31577a3 Binary files /dev/null and b/test/snackoo.png differ diff --git a/test/test_aopacket.cpp b/test/test_aopacket.cpp new file mode 100644 index 0000000..0b31821 --- /dev/null +++ b/test/test_aopacket.cpp @@ -0,0 +1,45 @@ +#define CATCH_CONFIG_MAIN +#include + +#include "aopacket.h" + +TEST_CASE("AOPacket construct", "[aopacket]") { + // Parameters + QString packet_string = "CT#MY_OOC_NAME#/doc https://docs.google.com/document/d/123/edit##%"; + + SECTION("Packet string") { + AOPacket p(packet_string); + REQUIRE(p.to_string() == packet_string); + } + SECTION("Header and contents") { + QStringList contents = {"MY_OOC_NAME", "/doc https://docs.google.com/document/d/123/edit#"}; + AOPacket p("CT", contents); + REQUIRE(p.to_string() == packet_string); + } +} + +TEST_CASE("AOPacket encode/decode", "[aopacket]") { + // Parameters + QString packet_string = "CT#MY_OOC_NAME#/doc https://docs.google.com/document/d/%$&/edit##%"; + QString good_encode = "CT#MY_OOC_NAME#/doc https://docs.google.com/document/d//edit#%"; + + SECTION("Bad encode/decode because packet string constructor splits the '#' after 'edit'") { + AOPacket p(packet_string); + p.net_encode(); + REQUIRE(p.to_string() != good_encode); + + p.net_decode(); + REQUIRE(p.to_string() == packet_string); + } + + SECTION("Good encode/decode with header and contents constructor") { + QStringList contents = {"MY_OOC_NAME", "/doc https://docs.google.com/document/d/%$&/edit#"}; + AOPacket p("CT", contents); + + p.net_encode(); + REQUIRE(p.to_string() == good_encode); + + p.net_decode(); + REQUIRE(p.to_string() == packet_string); + } +} diff --git a/test/test_apng.cpp b/test/test_apng.cpp new file mode 100644 index 0000000..20c7e92 --- /dev/null +++ b/test/test_apng.cpp @@ -0,0 +1,61 @@ +#include + +#include +#include +#include +#include +#include + +TEST_CASE("Support APNG Plugin", "[apng]") { + // Check paths for libs + QCoreApplication::addLibraryPath("."); + QCoreApplication::addLibraryPath("lib"); + + // Either it's loaded from system or we load local + QPluginLoader apngPlugin("qapng"); + apngPlugin.load(); + + INFO(QImageReader::supportedImageFormats().join(' ').toStdString()); + REQUIRE(QImageReader::supportedImageFormats().contains("apng")); +} + +TEST_CASE("Detect png animation", "[apng]") { + // Required for QPixmap methods + int argc = 1; + char bin[] = "test"; + char *argv[] = { bin }; + QGuiApplication app(argc, argv); + + // Instantiate reader + QImageReader reader; + + SECTION("Decide format from content fails on apng") { + reader.setFileName("snackoo.png"); + reader.setDecideFormatFromContent(true); + REQUIRE(!reader.supportsAnimation()); + REQUIRE(!QPixmap::fromImage(reader.read()).isNull()); + } + + SECTION("Auto detect fails on apng") { + reader.setFileName("snackoo.png"); + reader.setAutoDetectImageFormat(true); + REQUIRE(!reader.supportsAnimation()); + REQUIRE(!QPixmap::fromImage(reader.read()).isNull()); + } + + SECTION("Detect apng supports animation") { + reader.setFileName("snackoo.png"); + reader.setFormat("apng"); + REQUIRE(reader.supportsAnimation()); + REQUIRE(!QPixmap::fromImage(reader.read()).isNull()); + } + + SECTION("Detect png frame has no animation") { + reader.setFileName("missle.png"); + reader.setFormat("apng"); + REQUIRE(!reader.supportsAnimation()); + reader.setFormat("png"); + REQUIRE(!reader.supportsAnimation()); + REQUIRE(!QPixmap::fromImage(reader.read()).isNull()); + } +} diff --git a/test/test_bass.cpp b/test/test_bass.cpp new file mode 100644 index 0000000..f5f9198 --- /dev/null +++ b/test/test_bass.cpp @@ -0,0 +1,40 @@ +#include +#include + +#include +#include + +#include "bass.h" +#include "bassopus.h" + +TEST_CASE("BASS URL streaming", "[bass][noci]") { + // Sample + QString url = "https://raw.githubusercontent.com/skyedeving/aocharedit/master/Attorney%20Online%20Character%20Editor/Resources/about.mp3"; + + // initialize + BASS_Init(-1, 44100, 0, 0, nullptr); + + // create stream from url + HSTREAM stream; + unsigned int flags = BASS_STREAM_AUTOFREE | BASS_STREAM_STATUS; + if (url.endsWith(".opus")) { + stream = BASS_OPUS_StreamCreateURL(url.toStdString().c_str(), 0, flags, nullptr, 0); + } + else { + stream = BASS_StreamCreateURL(url.toStdString().c_str(), 0, flags, nullptr, 0); + } + + // Log http status + const char *tags = BASS_ChannelGetTags(stream, BASS_TAG_HTTP); + if (tags) { + while(*tags) { + UNSCOPED_INFO(tags); + tags += strlen(tags) + 1; + } + } + + // Test + REQUIRE(stream != 0); + REQUIRE(BASS_ChannelPlay(stream, TRUE) == TRUE); + // while (BASS_ChannelIsActive(stream) != BASS_ACTIVE_STOPPED); // block test to listen +} diff --git a/test/test_caseloading.cpp b/test/test_caseloading.cpp new file mode 100644 index 0000000..5df2782 --- /dev/null +++ b/test/test_caseloading.cpp @@ -0,0 +1,18 @@ +#include + +#include + +TEST_CASE("Sort case evidence numerically", "[case]") { + // Parameters + QStringList case_evidence = {"1", "10", "11", "2", "3", "4", "5", "6", "7", "8", "9"}; + QStringList case_evidence_sorted = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"}; + + // Sort + std::sort(case_evidence.begin(), case_evidence.end(), + [] (const QString &a, const QString &b) { + return a.toInt() < b.toInt(); + }); + + // Test + REQUIRE(case_evidence == case_evidence_sorted); +}