From 834ba36eb1c88f1618216a5bda922a81fb732407 Mon Sep 17 00:00:00 2001 From: Riley <25289999+riley-1995@users.noreply.github.com> Date: Tue, 10 Feb 2026 14:59:16 -0800 Subject: [PATCH 01/20] Enable blank issues and clear contact links Enabled blank issues and removed contact links. --- .github/ISSUE_TEMPLATE/config.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 00e82539..64eb98dc 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,2 @@ -blank_issues_enabled: false -contact_links: - - name: Create new issue - url: https://github.com/projectM-visualizer/projectm/issues/new/choose - about: Please open new issues in the main projectM repository. We will then move the issue into the correct repository for you. \ No newline at end of file +blank_issues_enabled: true +contact_links: [] From 68cf7f48e2a739023c38f5ad589221c8e4e97e06 Mon Sep 17 00:00:00 2001 From: akleinecke Date: Tue, 17 Feb 2026 19:49:00 -0800 Subject: [PATCH 02/20] Add integration test infrastructure and vcpkg manifest feature handling --- CMakeLists.txt | 32 +++++++++++++++++----- test/CMakeLists.txt | 0 tests/CMakeLists.txt | 41 ++++++++++++++++++++++++++++ tests/renderer/CMakeLists.txt | 18 ++++++++++++ tests/renderer/render-smoke-test.cpp | 20 ++++++++++++++ vcpkg.json | 11 ++++++-- 6 files changed, 113 insertions(+), 9 deletions(-) delete mode 100644 test/CMakeLists.txt create mode 100644 tests/CMakeLists.txt create mode 100644 tests/renderer/CMakeLists.txt create mode 100644 tests/renderer/render-smoke-test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d1f4ed0..4774918f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,16 +6,25 @@ set(CMAKE_POSITION_INDEPENDENT_CODE YES) set_property(GLOBAL PROPERTY USE_FOLDERS ON) -project(projectMSDL - LANGUAGES C CXX - VERSION 2.0.0 - ) - list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") option(ENABLE_FLAT_PACKAGE "Creates a \"flat\" install layout with the executable, configuration file(s) and preset/texture dirs directly in the install prefix." OFF) option(ENABLE_INSTALL_BDEPS "Installs all shared libraries projectMSDL requires to run. On some platforms, CMake 3.31 or higher is required for this to work!" OFF) +# Added option display to cmake build to allow testing +option(BUILD_TESTING "Build the frontend-sdl2 ctests" OFF) + +# Enable vcpkg manifest features according to build options (just like the projectm backend) +if(BUILD_TESTING) + list(APPEND VCPKG_MANIFEST_FEATURES test) +endif() + +# Moved below options to allow using options +project(projectMSDL + LANGUAGES C CXX + VERSION 2.0.0 +) + set(PROJECTMSDL_PROPERTIES_FILENAME "projectMSDL.properties") if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND NOT ENABLE_FLAT_PACKAGE) @@ -95,6 +104,7 @@ if(NOT SDL2_LINKAGE STREQUAL "shared" AND NOT SDL2_LINKAGE STREQUAL "static") ) endif() + find_package(projectM4 REQUIRED COMPONENTS Playlist) find_package(SDL2 REQUIRED) find_package(Poco REQUIRED COMPONENTS JSON XML Util Foundation) @@ -114,8 +124,10 @@ include(ImGui.cmake) add_subdirectory(src) -if(ENABLE_TESTING) - add_subdirectory(test) +# Adjusted testing build from existing code +if(BUILD_TESTING) + enable_testing() + add_subdirectory(tests) endif() include(install.cmake) @@ -135,3 +147,9 @@ message(STATUS "The projectMSDL binary will look for the following hard-coded pa message(STATUS " Configuration file: ${DEFAULT_CONFIG_PATH}") message(STATUS " Presets: ${DEFAULT_PRESETS_PATH}") message(STATUS " Textures: ${DEFAULT_TEXTURES_PATH}") + +# Added testing message +if (BUILD_TESTING) + message(STATUS "=============================================") + message(STATUS " Tests: ${BUILD_TESTING}") +endif() \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..f736ba5d --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,41 @@ +# ====== +# Test-only core library +# (Eventually this should be moved into src/CMakeLists.txt to avoid +# duplicating source lists and dependancies.) +# ====== + +# Build a core library from production sources to test on +add_library(projectMSDL_testcorelib + # Add .cpp files as needed for testing + # Examples: + # ${PROJECT_SOURCE_DIR}/src/SDLRenderingWindow.cpp + # ${PROJECT_SOURCE_DIR}/src/RenderLoop.cpp + # ${PROJECT_SOURCE_DIR}/src/ProjectMWrapper.cpp + # For now, we'll include a simple file just so this thing works. It can be removed if unneeded later. + ${PROJECT_SOURCE_DIR}/src/FPSLimiter.cpp +) + +# Headers from the production source tree +target_include_directories(projectMSDL_testcorelib + PUBLIC + ${PROJECT_SOURCE_DIR}/src +) + +# Compile definitions that are commonly referenced +target_compile_definitions(projectMSDL_testcorelib + PRIVATE + PROJECTMSDL_VERSION="${PROJECT_VERSION}" +) + +# Linker set (add to this only when the linker asks) +target_link_libraries(projectMSDL_testcorelib + PRIVATE + SDL2::SDL2$<$:-static> +) + +# ====== +# Actual test subdirs +# Add to this when defining more testing directories +# (To be kept in this file after integration of above code into src/CMakeLists.txt) +# ====== +add_subdirectory(renderer) \ No newline at end of file diff --git a/tests/renderer/CMakeLists.txt b/tests/renderer/CMakeLists.txt new file mode 100644 index 00000000..21af901c --- /dev/null +++ b/tests/renderer/CMakeLists.txt @@ -0,0 +1,18 @@ +# Requires gtest, otherwise fail +find_package(GTest 1.10 REQUIRED NO_MODULE) + +# Creation of executable for this specific test +add_executable(projectMsdl2-renderer-test + render-smoke-test.cpp +) + +# Call required libraries; testcore, gtest +target_link_libraries(projectMsdl2-renderer-test + PRIVATE + projectMSDL_testcorelib + GTest::gtest + GTest::gtest_main +) + +# Add our test to the executable +add_test(NAME projectMsdl2-renderer-test COMMAND projectMsdl2-renderer-test) \ No newline at end of file diff --git a/tests/renderer/render-smoke-test.cpp b/tests/renderer/render-smoke-test.cpp new file mode 100644 index 00000000..1e31ab0c --- /dev/null +++ b/tests/renderer/render-smoke-test.cpp @@ -0,0 +1,20 @@ +#include + +// All of the below is to be replaced later, these are just placeholders for testing + +// Test if linking works +#include "FPSLimiter.h" + +// Simple smoke test +TEST(SmokeTest, BasicMathTest) +{ + EXPECT_EQ(2 + 2, 4); +} + +TEST(SmokeTest, CanConstructFPSLimiterClass) +{ + // This just proves headers compile and symbols link + FPSLimiter limiter; + + SUCCEED(); +} \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json index 924009d6..a3f8a535 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -9,7 +9,14 @@ "util" ] }, - "projectm", "freetype" - ] + ], + "features": { + "test": { + "description": "Build unit tests", + "dependencies": [ + "gtest" + ] + } + } } \ No newline at end of file From 137e5c89fb719ba538054a547d8acb06cff098a5 Mon Sep 17 00:00:00 2001 From: riley-1995 Date: Wed, 18 Feb 2026 12:43:05 -0800 Subject: [PATCH 03/20] Fix SDL2 include path handling and simplify test linkage --- cmake/SDL2Target.cmake | 34 ++++++++++++++++++++++------------ tests/CMakeLists.txt | 2 +- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/cmake/SDL2Target.cmake b/cmake/SDL2Target.cmake index a949078f..2c759965 100644 --- a/cmake/SDL2Target.cmake +++ b/cmake/SDL2Target.cmake @@ -54,20 +54,30 @@ if(NOT TARGET SDL2::SDL2) endif() # Temporary fix to deal with wrong include dir set by SDL2's CMake configuration. -get_target_property(_SDL2_INCLUDE_DIR SDL2::SDL2 INTERFACE_INCLUDE_DIRECTORIES) -if(_SDL2_INCLUDE_DIR MATCHES "(.+)/SDL2\$" AND _SDL2_TARGET_TYPE STREQUAL STATIC_LIBRARY) - # Check if SDL2::SDL2 is aliased to SDL2::SDL2-static (will be the case for static-only builds) - get_target_property(_SDL2_ALIASED_TARGET SDL2::SDL2 ALIASED_TARGET) - if(_SDL2_ALIASED_TARGET) - set(_sdl2_target ${_SDL2_ALIASED_TARGET}) - else() - set(_sdl2_target SDL2::SDL2) - endif() +# Some SDL2 configs incorrectly report .../include/SDL2 instead of .../include. +get_target_property(_SDL2_INCLUDE_DIRS SDL2::SDL2 INTERFACE_INCLUDE_DIRECTORIES) + +if(_SDL2_INCLUDE_DIRS) + # The property can be a list, so handle each entry. + foreach(_dir IN LISTS _SDL2_INCLUDE_DIRS) + if(_dir MATCHES "(.+)/SDL2$") + set(_fixed_parent "${CMAKE_MATCH_1}") + + # If SDL2::SDL2 is an alias, patch the real target. + get_target_property(_SDL2_ALIASED_TARGET SDL2::SDL2 ALIASED_TARGET) + if(_SDL2_ALIASED_TARGET) + set(_sdl2_target "${_SDL2_ALIASED_TARGET}") + else() + set(_sdl2_target SDL2::SDL2) + endif() - message(STATUS "SDL2 include dir contains \"SDL2\" subdir (SDL bug #4004) - fixing to \"${CMAKE_MATCH_1}\".") - set_target_properties(${_sdl2_target} PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_MATCH_1}" + message(STATUS "SDL2 include dir contains \"SDL2\" subdir - fixing to \"${_fixed_parent}\".") + set_target_properties(${_sdl2_target} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${_fixed_parent}" ) + break() + endif() + endforeach() endif() if(SDL2_VERSION AND SDL2_VERSION VERSION_LESS "2.0.5") diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f736ba5d..3b7e8e5c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -30,7 +30,7 @@ target_compile_definitions(projectMSDL_testcorelib # Linker set (add to this only when the linker asks) target_link_libraries(projectMSDL_testcorelib PRIVATE - SDL2::SDL2$<$:-static> + SDL2::SDL2 ) # ====== From 62b5d7bd7a3428296bf6bddd55f58a5267c0f7f4 Mon Sep 17 00:00:00 2001 From: riley-1995 Date: Wed, 18 Feb 2026 13:33:12 -0800 Subject: [PATCH 04/20] CI: fix vcpkg nuget invocation in Windows workflow --- .github/workflows/buildcheck.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/buildcheck.yaml b/.github/workflows/buildcheck.yaml index cbf58fee..53626073 100644 --- a/.github/workflows/buildcheck.yaml +++ b/.github/workflows/buildcheck.yaml @@ -115,15 +115,14 @@ jobs: - name: Add NuGet sources shell: pwsh run: | - .$(${{ env.VCPKG_EXE }} fetch nuget) ` - sources add ` + $nuget = (& "${{ env.VCPKG_EXE }}" fetch nuget | Select-Object -Last 1).Trim() + & $nuget sources add ` -Source "${{ env.FEED_URL }}" ` -StorePasswordInClearText ` -Name GitHubPackages ` -UserName "${{ env.USERNAME }}" ` -Password "${{ secrets.VCPKG_PACKAGES_TOKEN }}" - .$(${{ env.VCPKG_EXE }} fetch nuget) ` - setapikey "${{ secrets.VCPKG_PACKAGES_TOKEN }}" ` + & $nuget setapikey "${{ secrets.VCPKG_PACKAGES_TOKEN }}" ` -Source "${{ env.FEED_URL }}" - name: Checkout libprojectM Sources From 19e68be55097ce5b2975a2964384d415cf38a8fc Mon Sep 17 00:00:00 2001 From: akleinecke Date: Wed, 18 Feb 2026 17:27:23 -0800 Subject: [PATCH 05/20] Modified to adjust to review suggestions. --- CMakeLists.txt | 8 ++------ tests/renderer/CMakeLists.txt | 8 ++++---- .../{render-smoke-test.cpp => RenderSmokeTest.cpp} | 0 3 files changed, 6 insertions(+), 10 deletions(-) rename tests/renderer/{render-smoke-test.cpp => RenderSmokeTest.cpp} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4774918f..0a2359c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,7 +104,6 @@ if(NOT SDL2_LINKAGE STREQUAL "shared" AND NOT SDL2_LINKAGE STREQUAL "static") ) endif() - find_package(projectM4 REQUIRED COMPONENTS Playlist) find_package(SDL2 REQUIRED) find_package(Poco REQUIRED COMPONENTS JSON XML Util Foundation) @@ -147,9 +146,6 @@ message(STATUS "The projectMSDL binary will look for the following hard-coded pa message(STATUS " Configuration file: ${DEFAULT_CONFIG_PATH}") message(STATUS " Presets: ${DEFAULT_PRESETS_PATH}") message(STATUS " Textures: ${DEFAULT_TEXTURES_PATH}") - # Added testing message -if (BUILD_TESTING) - message(STATUS "=============================================") - message(STATUS " Tests: ${BUILD_TESTING}") -endif() \ No newline at end of file +message(STATUS "=============================================") +message(STATUS " Tests: ${BUILD_TESTING}") \ No newline at end of file diff --git a/tests/renderer/CMakeLists.txt b/tests/renderer/CMakeLists.txt index 21af901c..7a51ba0e 100644 --- a/tests/renderer/CMakeLists.txt +++ b/tests/renderer/CMakeLists.txt @@ -2,12 +2,12 @@ find_package(GTest 1.10 REQUIRED NO_MODULE) # Creation of executable for this specific test -add_executable(projectMsdl2-renderer-test - render-smoke-test.cpp +add_executable(projectMSDL-renderer-test + RenderSmokeTest.cpp ) # Call required libraries; testcore, gtest -target_link_libraries(projectMsdl2-renderer-test +target_link_libraries(projectMSDL-renderer-test PRIVATE projectMSDL_testcorelib GTest::gtest @@ -15,4 +15,4 @@ target_link_libraries(projectMsdl2-renderer-test ) # Add our test to the executable -add_test(NAME projectMsdl2-renderer-test COMMAND projectMsdl2-renderer-test) \ No newline at end of file +add_test(NAME projectMSDL-renderer-test COMMAND projectMSDL-renderer-test) \ No newline at end of file diff --git a/tests/renderer/render-smoke-test.cpp b/tests/renderer/RenderSmokeTest.cpp similarity index 100% rename from tests/renderer/render-smoke-test.cpp rename to tests/renderer/RenderSmokeTest.cpp From c08fad576ccdf88bad755d026960d8cf2d546d8a Mon Sep 17 00:00:00 2001 From: riley-1995 Date: Tue, 17 Feb 2026 18:17:27 -0800 Subject: [PATCH 06/20] Implement audio devices override v1 Add ListAudioDevices test and update test core lib --- tests/CMakeLists.txt | 16 +++++++++++++++- tests/audio/CMakeLists.txt | 19 +++++++++++++++++++ tests/audio/ListAudioDevicesTest.cpp | 21 +++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/audio/CMakeLists.txt create mode 100644 tests/audio/ListAudioDevicesTest.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3b7e8e5c..5db813ae 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -13,6 +13,12 @@ add_library(projectMSDL_testcorelib # ${PROJECT_SOURCE_DIR}/src/ProjectMWrapper.cpp # For now, we'll include a simple file just so this thing works. It can be removed if unneeded later. ${PROJECT_SOURCE_DIR}/src/FPSLimiter.cpp + ${PROJECT_SOURCE_DIR}/src/ProjectMSDLApplication.cpp + ${PROJECT_SOURCE_DIR}/src/RenderLoop.cpp + ${PROJECT_SOURCE_DIR}/src/ProjectMWrapper.cpp + ${PROJECT_SOURCE_DIR}/src/SDLRenderingWindow.cpp + ${PROJECT_SOURCE_DIR}/src/AudioCapture.cpp + ${PROJECT_SOURCE_DIR}/src/AudioCaptureImpl_SDL.cpp ) # Headers from the production source tree @@ -24,13 +30,20 @@ target_include_directories(projectMSDL_testcorelib # Compile definitions that are commonly referenced target_compile_definitions(projectMSDL_testcorelib PRIVATE + PROJECTMSDL_CONFIG_LOCATION="${DEFAULT_CONFIG_PATH}" PROJECTMSDL_VERSION="${PROJECT_VERSION}" + AUDIO_IMPL_HEADER="AudioCaptureImpl_SDL.h" ) # Linker set (add to this only when the linker asks) target_link_libraries(projectMSDL_testcorelib PRIVATE SDL2::SDL2 + Poco::Util + Poco::Foundation + ProjectMSDL-GUI + ProjectMSDL-Notifications + libprojectM::playlist ) # ====== @@ -38,4 +51,5 @@ target_link_libraries(projectMSDL_testcorelib # Add to this when defining more testing directories # (To be kept in this file after integration of above code into src/CMakeLists.txt) # ====== -add_subdirectory(renderer) \ No newline at end of file +add_subdirectory(renderer) +add_subdirectory(audio) \ No newline at end of file diff --git a/tests/audio/CMakeLists.txt b/tests/audio/CMakeLists.txt new file mode 100644 index 00000000..609ba0e8 --- /dev/null +++ b/tests/audio/CMakeLists.txt @@ -0,0 +1,19 @@ +find_package(GTest 1.10 REQUIRED NO_MODULE) + +add_executable(projectMSDL-audio-tests + ListAudioDevicesTest.cpp +) + +target_include_directories(projectMSDL-audio-tests + PRIVATE + ${PROJECT_SOURCE_DIR}/src +) + +target_link_libraries(projectMSDL-audio-tests + PRIVATE + projectMSDL_testcorelib + GTest::gtest + GTest::gtest_main +) + +add_test(NAME projectMSDL-audio-tests COMMAND projectMSDL-audio-tests) \ No newline at end of file diff --git a/tests/audio/ListAudioDevicesTest.cpp b/tests/audio/ListAudioDevicesTest.cpp new file mode 100644 index 00000000..492ce84d --- /dev/null +++ b/tests/audio/ListAudioDevicesTest.cpp @@ -0,0 +1,21 @@ +#include +#include "ProjectMSDLApplication.h" + +// Expose the protected ListAudioDevices for testing by deriving a small test subclass. +class TestableProjectMSDLApplication : public ProjectMSDLApplication { +public: + using ProjectMSDLApplication::ListAudioDevices; // make protected method public in test subclass +}; + +TEST(ListAudioDevicesTest, SetsAudioListDevicesOverrideToTrue) +{ + TestableProjectMSDLApplication app; + + auto cfg = app.CommandLineConfiguration(); + ASSERT_TRUE(cfg); // check configuration object exists + + // Call the option handler directly (name and value are unused in implementation). + app.ListAudioDevices("", ""); + + EXPECT_TRUE(cfg->getBool("audio.listDevices", false)); +} \ No newline at end of file From 46f5161c58573437a3055a526b69b4936ad6d1a1 Mon Sep 17 00:00:00 2001 From: riley-1995 Date: Mon, 23 Feb 2026 13:23:54 -0800 Subject: [PATCH 07/20] Update tests/CMakeLists.txt to specify windows vs non windows audio capture implementation file. --- tests/CMakeLists.txt | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5db813ae..57168054 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,9 +18,25 @@ add_library(projectMSDL_testcorelib ${PROJECT_SOURCE_DIR}/src/ProjectMWrapper.cpp ${PROJECT_SOURCE_DIR}/src/SDLRenderingWindow.cpp ${PROJECT_SOURCE_DIR}/src/AudioCapture.cpp - ${PROJECT_SOURCE_DIR}/src/AudioCaptureImpl_SDL.cpp ) +if (WIN32) + target_sources(projectMSDL_testcorelib PRIVATE + ${PROJECT_SOURCE_DIR}/src/AudioCaptureImpl_WASAPI.cpp + ) + target_compile_definitions(projectMSDL_testcorelib PRIVATE + AUDIO_IMPL_HEADER="AudioCaptureImpl_WASAPI.h" + USE_GLEW + ) +else() + target_sources(projectMSDL_testcorelib PRIVATE + ${PROJECT_SOURCE_DIR}/src/AudioCaptureImpl_SDL.cpp + ) + target_compile_definitions(projectMSDL_testcorelib PRIVATE + AUDIO_IMPL_HEADER="AudioCaptureImpl_SDL.h" + ) +endif() + # Headers from the production source tree target_include_directories(projectMSDL_testcorelib PUBLIC @@ -32,7 +48,6 @@ target_compile_definitions(projectMSDL_testcorelib PRIVATE PROJECTMSDL_CONFIG_LOCATION="${DEFAULT_CONFIG_PATH}" PROJECTMSDL_VERSION="${PROJECT_VERSION}" - AUDIO_IMPL_HEADER="AudioCaptureImpl_SDL.h" ) # Linker set (add to this only when the linker asks) From 5a59ae8b1fc3987b67a9c46ac67a1bf9c955eed4 Mon Sep 17 00:00:00 2001 From: riley-1995 Date: Mon, 23 Feb 2026 18:50:34 -0800 Subject: [PATCH 08/20] Update List Audio Devices test to be more robust in checking listDevices config before and after calling ListAudioDevices(). --- tests/audio/ListAudioDevicesTest.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/audio/ListAudioDevicesTest.cpp b/tests/audio/ListAudioDevicesTest.cpp index 492ce84d..0af1dffc 100644 --- a/tests/audio/ListAudioDevicesTest.cpp +++ b/tests/audio/ListAudioDevicesTest.cpp @@ -14,8 +14,17 @@ TEST(ListAudioDevicesTest, SetsAudioListDevicesOverrideToTrue) auto cfg = app.CommandLineConfiguration(); ASSERT_TRUE(cfg); // check configuration object exists + EXPECT_FALSE(cfg->getBool("audio.listDevices", false)); + // Call the option handler directly (name and value are unused in implementation). app.ListAudioDevices("", ""); + // Check to see if config has been correctly overridden + EXPECT_TRUE(cfg->getBool("audio.listDevices", false)); + + // Get config again, to test using a fresh config object + auto cfg = app.CommandLineConfiguration(); + ASSERT_TRUE(cfg); // check configuration object exists + EXPECT_TRUE(cfg->getBool("audio.listDevices", false)); } \ No newline at end of file From c4233703ca70f8214b9c1c144d5ef34b5742c99d Mon Sep 17 00:00:00 2001 From: riley-1995 Date: Mon, 23 Feb 2026 18:57:16 -0800 Subject: [PATCH 09/20] Fixed error in ListAudioDevicesTest for redeclaring cfg by renaming variable on 2nd declaration. --- tests/audio/ListAudioDevicesTest.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/audio/ListAudioDevicesTest.cpp b/tests/audio/ListAudioDevicesTest.cpp index 0af1dffc..1dc86671 100644 --- a/tests/audio/ListAudioDevicesTest.cpp +++ b/tests/audio/ListAudioDevicesTest.cpp @@ -23,8 +23,8 @@ TEST(ListAudioDevicesTest, SetsAudioListDevicesOverrideToTrue) EXPECT_TRUE(cfg->getBool("audio.listDevices", false)); // Get config again, to test using a fresh config object - auto cfg = app.CommandLineConfiguration(); - ASSERT_TRUE(cfg); // check configuration object exists + auto cfg2 = app.CommandLineConfiguration(); + ASSERT_TRUE(cfg2); // check configuration object exists - EXPECT_TRUE(cfg->getBool("audio.listDevices", false)); + EXPECT_TRUE(cfg2->getBool("audio.listDevices", false)); } \ No newline at end of file From 94bf6679be197c291d215ce942a22d30d50856d0 Mon Sep 17 00:00:00 2001 From: riley-1995 Date: Mon, 23 Feb 2026 22:09:16 -0800 Subject: [PATCH 10/20] Update build check workflow to optimize for windows on forks via caching. --- .github/workflows/buildcheck.yaml | 64 ++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/.github/workflows/buildcheck.yaml b/.github/workflows/buildcheck.yaml index 53626073..532bc0ac 100644 --- a/.github/workflows/buildcheck.yaml +++ b/.github/workflows/buildcheck.yaml @@ -97,10 +97,20 @@ jobs: USERNAME: projectM-visualizer VCPKG_EXE: ${{ github.workspace }}/vcpkg/vcpkg FEED_URL: https://nuget.pkg.github.com/projectM-visualizer/index.json - VCPKG_BINARY_SOURCES: "clear;nuget,https://nuget.pkg.github.com/projectM-visualizer/index.json,readwrite" - steps: + - name: Configure vcpkg binary source mode + shell: pwsh + run: | + if ("${{ github.repository }}" -eq "projectM-visualizer/frontend-sdl-cpp") { + Write-Host "Using readwrite binary cache (upstream repo)" + "VCPKG_BINARY_SOURCES=clear;nuget,${{ env.FEED_URL }},readwrite" | Out-File -FilePath $env:GITHUB_ENV -Append + } + else { + Write-Host "Using read-only binary cache (fork or PR)" + "VCPKG_BINARY_SOURCES=clear;nuget,${{ env.FEED_URL }},read" | Out-File -FilePath $env:GITHUB_ENV -Append + } + - name: Checkout vcpkg uses: actions/checkout@v4 with: @@ -108,21 +118,61 @@ jobs: path: vcpkg submodules: recursive + # Capture the exact vcpkg repository commit being used. + # This ensures our cache key changes automatically if vcpkg itself is updated. + - name: Get vcpkg commit + id: vcpkg_rev + shell: pwsh + run: | + $rev = (git -C "${{ github.workspace }}/vcpkg" rev-parse HEAD).Trim() + "rev=$rev" | Out-File -FilePath $env:GITHUB_OUTPUT -Append + + # Needed before cache so hashFiles('**/vcpkg.json', ...) actually sees these files. + - name: Checkout projectMSDL Sources + uses: actions/checkout@v4 + with: + path: frontend-sdl2 + submodules: recursive + + # Cache downloaded sources and compiled packages so vcpkg does not rebuild all dependencies on every CI run. + - name: Cache vcpkg artifacts + uses: actions/cache@v4 + with: + path: | + ${{ github.workspace }}/vcpkg/downloads + ${{ github.workspace }}/vcpkg/buildtrees + ${{ github.workspace }}/vcpkg/packages + # Cache key includes OS, vcpkg commit, and dependency manifest hash. + # Any change to vcpkg version or dependencies automatically invalidates cache. + key: vcpkg-${{ runner.os }}-${{ steps.vcpkg_rev.outputs.rev }}-${{ hashFiles('frontend-sdl2/vcpkg.json', 'frontend-sdl2/vcpkg-configuration.json') }} + # Fallback keys allow partial reuse if only dependency hash changed. + restore-keys: | + vcpkg-${{ runner.os }}-${{ steps.vcpkg_rev.outputs.rev }}- + vcpkg-${{ runner.os }}- + - name: Bootstrap vcpkg shell: pwsh run: ${{ github.workspace }}/vcpkg/bootstrap-vcpkg.bat - name: Add NuGet sources + if: github.repository == 'projectM-visualizer/frontend-sdl-cpp' + env: + VCPKG_PACKAGES_TOKEN: ${{ secrets.VCPKG_PACKAGES_TOKEN }} shell: pwsh run: | + if (-not $env:VCPKG_PACKAGES_TOKEN) { + Write-Host "VCPKG_PACKAGES_TOKEN not set; skipping NuGet auth." + exit 0 + } + $nuget = (& "${{ env.VCPKG_EXE }}" fetch nuget | Select-Object -Last 1).Trim() & $nuget sources add ` -Source "${{ env.FEED_URL }}" ` -StorePasswordInClearText ` -Name GitHubPackages ` -UserName "${{ env.USERNAME }}" ` - -Password "${{ secrets.VCPKG_PACKAGES_TOKEN }}" - & $nuget setapikey "${{ secrets.VCPKG_PACKAGES_TOKEN }}" ` + -Password "$env:VCPKG_PACKAGES_TOKEN" + & $nuget setapikey "$env:VCPKG_PACKAGES_TOKEN" ` -Source "${{ env.FEED_URL }}" - name: Checkout libprojectM Sources @@ -139,12 +189,6 @@ jobs: cmake --build "${{ github.workspace }}/cmake-build-libprojectm" --config Release --parallel cmake --install "${{ github.workspace }}/cmake-build-libprojectm" --config Release - - name: Checkout projectMSDL Sources - uses: actions/checkout@v4 - with: - path: frontend-sdl2 - submodules: recursive - - name: Build projectMSDL run: | mkdir cmake-build-frontend-sdl2 From 0079a940ca3b9e210f9cb2bacab210aa8122f697 Mon Sep 17 00:00:00 2001 From: tooth-juice Date: Mon, 23 Feb 2026 22:32:32 -0800 Subject: [PATCH 11/20] Add integration render smoke test using glReadPixels. --- tests/renderer/RenderSmokeTest.cpp | 97 ++++++++++++++++++++++++++---- vcpkg.json | 1 + 2 files changed, 86 insertions(+), 12 deletions(-) diff --git a/tests/renderer/RenderSmokeTest.cpp b/tests/renderer/RenderSmokeTest.cpp index 1e31ab0c..c994d80f 100644 --- a/tests/renderer/RenderSmokeTest.cpp +++ b/tests/renderer/RenderSmokeTest.cpp @@ -1,20 +1,93 @@ #include +#include "ProjectMSDLApplication.h" +#include "SDLRenderingWindow.h" +#include "ProjectMWrapper.h" -// All of the below is to be replaced later, these are just placeholders for testing +#include +#include +#include -// Test if linking works -#include "FPSLimiter.h" -// Simple smoke test -TEST(SmokeTest, BasicMathTest) -{ - EXPECT_EQ(2 + 2, 4); +// Helper function: +// Count how many pixels are NOT black +// Epsilon is our value for not black. Anything less than 2 on the GPU is black (some GPU's don't see 0,0,0 as black). +static size_t CountNonBlackPixels(const std::vector& rgba, unsigned char epsilon = 2) { + size_t count = 0; + for (size_t i = 0; i + 2 < rgba.size(); i += 4) { + if (rgba[i] > epsilon || rgba[i + 1] > epsilon || rgba[i + 2] > epsilon) + ++count; + } + return count; } -TEST(SmokeTest, CanConstructFPSLimiterClass) -{ - // This just proves headers compile and symbols link - FPSLimiter limiter; +// Integration test: +// Verifies that: +// - App can create a window and OpenGL context +// - Renderer can draw something +// - Output is not just a blank screen +TEST(RenderSmokeTest, AppRendersSomething) { +// Tell SDL to use a dummy audio driver so audio init doesn't fail on Linux/MacOS +#if !defined(_WIN32) + setenv("SDL_AUDIODRIVER", "dummy", 1); +#endif - SUCCEED(); + // Simulate starting the real app + const char* argv0 = "projectMSDL"; + int argc = 1; + char* argv[] = { const_cast(argv0), nullptr }; + + // Create real app obj + ProjectMSDLApplication app; + + // Init the app + ASSERT_NO_THROW(app.init(argc, argv)); + + // Create SDL window + OpenGL context + auto& window = app.getSubsystem(); + ASSERT_NO_THROW(window.initialize(app)); + + // Give SDL a moment / pump events + SDL_PumpEvents(); + + // Create the projectM renderer + auto& renderer = app.getSubsystem(); + ASSERT_NO_THROW(renderer.initialize(app)); + + // Get the size of the drawable OpenGL area + int width = 0, height = 0; + window.GetDrawableSize(width, height); + + // Fail if H/W are 0 + ASSERT_GT(width, 0); + ASSERT_GT(height, 0); + + // Render a few frames + for (int i = 0; i < 3; ++i) { + renderer.RenderFrame(); + glFinish(); + } + + // Prepare buffer and read pixels + const size_t bufSize = static_cast(width) * static_cast(height) * 4; + std::vector pixels(bufSize); + + glPixelStorei(GL_PACK_ALIGNMENT, 1); // safe alignment for glReadPixels + + // Try reading back from the back buffer first + glReadBuffer(GL_BACK); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + glFinish(); + + // Count non black pixels in back buffer + size_t nonBlack = CountNonBlackPixels(pixels); + + // Threshold: at least 100 pixels OR at least 0.2% of the image + size_t threshold = static_cast(width) * static_cast(height) / 500; + if (threshold < 100) threshold = 100; + + EXPECT_GT(nonBlack, threshold); + + // Clean up + renderer.uninitialize(); + window.uninitialize(); } \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json index a3f8a535..d360dddf 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -9,6 +9,7 @@ "util" ] }, + "projectm", "freetype" ], "features": { From 4e253d06074ae0bc5cd316c26c2ad95cf503eb9f Mon Sep 17 00:00:00 2001 From: riley-1995 Date: Mon, 23 Feb 2026 23:49:04 -0800 Subject: [PATCH 12/20] Update buildcheck windows: add file-based vcpkg binary cache for forks and NuGet auth for upstream. --- .github/workflows/buildcheck.yaml | 67 ++++++++++++++++++------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/.github/workflows/buildcheck.yaml b/.github/workflows/buildcheck.yaml index 532bc0ac..eeab3efc 100644 --- a/.github/workflows/buildcheck.yaml +++ b/.github/workflows/buildcheck.yaml @@ -99,18 +99,6 @@ jobs: FEED_URL: https://nuget.pkg.github.com/projectM-visualizer/index.json steps: - - name: Configure vcpkg binary source mode - shell: pwsh - run: | - if ("${{ github.repository }}" -eq "projectM-visualizer/frontend-sdl-cpp") { - Write-Host "Using readwrite binary cache (upstream repo)" - "VCPKG_BINARY_SOURCES=clear;nuget,${{ env.FEED_URL }},readwrite" | Out-File -FilePath $env:GITHUB_ENV -Append - } - else { - Write-Host "Using read-only binary cache (fork or PR)" - "VCPKG_BINARY_SOURCES=clear;nuget,${{ env.FEED_URL }},read" | Out-File -FilePath $env:GITHUB_ENV -Append - } - - name: Checkout vcpkg uses: actions/checkout@v4 with: @@ -127,33 +115,54 @@ jobs: $rev = (git -C "${{ github.workspace }}/vcpkg" rev-parse HEAD).Trim() "rev=$rev" | Out-File -FilePath $env:GITHUB_OUTPUT -Append - # Needed before cache so hashFiles('**/vcpkg.json', ...) actually sees these files. + # Checkout this repo before hashFiles() / manifest is referenced - name: Checkout projectMSDL Sources uses: actions/checkout@v4 with: path: frontend-sdl2 submodules: recursive - # Cache downloaded sources and compiled packages so vcpkg does not rebuild all dependencies on every CI run. - - name: Cache vcpkg artifacts + # Restore fork cache (file-based binary cache persisted by actions/cache) + - name: Cache vcpkg binary cache (forks) + if: github.repository != 'projectM-visualizer/frontend-sdl-cpp' uses: actions/cache@v4 with: - path: | - ${{ github.workspace }}/vcpkg/downloads - ${{ github.workspace }}/vcpkg/buildtrees - ${{ github.workspace }}/vcpkg/packages - # Cache key includes OS, vcpkg commit, and dependency manifest hash. - # Any change to vcpkg version or dependencies automatically invalidates cache. - key: vcpkg-${{ runner.os }}-${{ steps.vcpkg_rev.outputs.rev }}-${{ hashFiles('frontend-sdl2/vcpkg.json', 'frontend-sdl2/vcpkg-configuration.json') }} - # Fallback keys allow partial reuse if only dependency hash changed. + path: ${{ github.workspace }}\vcpkg-binary-cache + key: vcpkg-bincache-${{ runner.os }}-${{ steps.vcpkg_rev.outputs.rev }}-${{ hashFiles('frontend-sdl2/vcpkg.json', 'frontend-sdl2/vcpkg-configuration.json') }} restore-keys: | - vcpkg-${{ runner.os }}-${{ steps.vcpkg_rev.outputs.rev }}- - vcpkg-${{ runner.os }}- + vcpkg-bincache-${{ runner.os }}-${{ steps.vcpkg_rev.outputs.rev }}- + vcpkg-bincache-${{ runner.os }}- - - name: Bootstrap vcpkg + - name: Show if binary cache has contents (forks) + if: github.repository != 'projectM-visualizer/frontend-sdl-cpp' shell: pwsh - run: ${{ github.workspace }}/vcpkg/bootstrap-vcpkg.bat + run: | + $cacheDir = Join-Path $env:GITHUB_WORKSPACE "vcpkg-binary-cache" + if (Test-Path $cacheDir) { + Get-ChildItem -Recurse $cacheDir | Select-Object -First 30 FullName + } else { + Write-Host "Binary cache directory does not exist." + } + + # Configure vcpkg binary cache behavior + - name: Configure vcpkg binary cache (upstream) + if: github.repository == 'projectM-visualizer/frontend-sdl-cpp' + shell: pwsh + run: | + Write-Host "Using NuGet binary cache (readwrite) for upstream repo" + "VCPKG_BINARY_SOURCES=clear;nuget,${{ env.FEED_URL }},readwrite" | Out-File -FilePath $env:GITHUB_ENV -Append + + - name: Configure vcpkg binary cache (forks) + if: github.repository != 'projectM-visualizer/frontend-sdl-cpp' + shell: pwsh + run: | + Write-Host "Using local file-based binary cache (readwrite) for fork/PR" + $cacheDir = Join-Path $env:GITHUB_WORKSPACE "vcpkg-binary-cache" + New-Item -ItemType Directory -Force -Path $cacheDir | Out-Null + "VCPKG_DEFAULT_BINARY_CACHE=$cacheDir" | Out-File -FilePath $env:GITHUB_ENV -Append + "VCPKG_BINARY_SOURCES=clear;files,$cacheDir,readwrite" | Out-File -FilePath $env:GITHUB_ENV -Append + # Upstream-only: authenticate to GitHub Packages NuGet feed - name: Add NuGet sources if: github.repository == 'projectM-visualizer/frontend-sdl-cpp' env: @@ -175,6 +184,10 @@ jobs: & $nuget setapikey "$env:VCPKG_PACKAGES_TOKEN" ` -Source "${{ env.FEED_URL }}" + - name: Bootstrap vcpkg + shell: pwsh + run: ${{ github.workspace }}/vcpkg/bootstrap-vcpkg.bat + - name: Checkout libprojectM Sources uses: actions/checkout@v4 with: From de9e4d16250d4dfeb30c141f2467a95b100b6653 Mon Sep 17 00:00:00 2001 From: riley-1995 Date: Tue, 24 Feb 2026 00:50:42 -0800 Subject: [PATCH 13/20] Prepare vcpkg cache dir before attempting cache restore for forks --- .github/workflows/buildcheck.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/buildcheck.yaml b/.github/workflows/buildcheck.yaml index eeab3efc..1feaf4ce 100644 --- a/.github/workflows/buildcheck.yaml +++ b/.github/workflows/buildcheck.yaml @@ -122,6 +122,14 @@ jobs: path: frontend-sdl2 submodules: recursive + - name: Prepare vcpkg binary cache dir (forks) + if: github.repository != 'projectM-visualizer/frontend-sdl-cpp' + shell: pwsh + run: | + $cacheDir = Join-Path $env:GITHUB_WORKSPACE "vcpkg-binary-cache" + New-Item -ItemType Directory -Force -Path $cacheDir | Out-Null + Write-Host "Cache dir: $cacheDir" + # Restore fork cache (file-based binary cache persisted by actions/cache) - name: Cache vcpkg binary cache (forks) if: github.repository != 'projectM-visualizer/frontend-sdl-cpp' @@ -158,7 +166,6 @@ jobs: run: | Write-Host "Using local file-based binary cache (readwrite) for fork/PR" $cacheDir = Join-Path $env:GITHUB_WORKSPACE "vcpkg-binary-cache" - New-Item -ItemType Directory -Force -Path $cacheDir | Out-Null "VCPKG_DEFAULT_BINARY_CACHE=$cacheDir" | Out-File -FilePath $env:GITHUB_ENV -Append "VCPKG_BINARY_SOURCES=clear;files,$cacheDir,readwrite" | Out-File -FilePath $env:GITHUB_ENV -Append From ce05ff1c559280976a982237fc686e648aa2f991 Mon Sep 17 00:00:00 2001 From: akleinecke Date: Tue, 24 Feb 2026 13:32:57 -0800 Subject: [PATCH 14/20] Adhere to peer review --- tests/renderer/RenderSmokeTest.cpp | 56 +++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/tests/renderer/RenderSmokeTest.cpp b/tests/renderer/RenderSmokeTest.cpp index c994d80f..c9420ec5 100644 --- a/tests/renderer/RenderSmokeTest.cpp +++ b/tests/renderer/RenderSmokeTest.cpp @@ -3,10 +3,28 @@ #include "SDLRenderingWindow.h" #include "ProjectMWrapper.h" +#if defined(_WIN32) + #include +#endif + #include #include #include +struct RendererSmokeCleanup { + ProjectMWrapper* renderer = nullptr; + SDLRenderingWindow* window = nullptr; + + ~RendererSmokeCleanup() { + if (renderer) renderer->uninitialize(); + if (window) window->uninitialize(); + } +}; + +auto CheckGLError = [] (const char* where) { + GLenum err = glGetError(); + ASSERT_EQ(err, GL_NO_ERROR) << "OpenGL error at " << where << ": 0x" << std::hex << err; +}; // Helper function: // Count how many pixels are NOT black @@ -44,13 +62,20 @@ TEST(RenderSmokeTest, AppRendersSomething) { // Create SDL window + OpenGL context auto& window = app.getSubsystem(); - ASSERT_NO_THROW(window.initialize(app)); // Give SDL a moment / pump events SDL_PumpEvents(); // Create the projectM renderer auto& renderer = app.getSubsystem(); + + // Create a cleaner + RendererSmokeCleanup cleanup; + cleanup.window = &window; + cleanup.renderer = &renderer; + + // Make sure these actually initialized correctly + ASSERT_NO_THROW(window.initialize(app)); ASSERT_NO_THROW(renderer.initialize(app)); // Get the size of the drawable OpenGL area @@ -61,22 +86,39 @@ TEST(RenderSmokeTest, AppRendersSomething) { ASSERT_GT(width, 0); ASSERT_GT(height, 0); + // Make sure our OpenGL window is clear first + glViewport(0, 0, width, height); + glClearColor(0.f, 0.f, 0.f, 1.f); + glClear(GL_COLOR_BUFFER_BIT); + glFinish(); + CheckGLError("ClearColor"); + // Render a few frames - for (int i = 0; i < 3; ++i) { + for (int i = 0; i < 20; ++i) { renderer.RenderFrame(); glFinish(); + CheckGLError("RenderFrame"); } // Prepare buffer and read pixels const size_t bufSize = static_cast(width) * static_cast(height) * 4; std::vector pixels(bufSize); - glPixelStorei(GL_PACK_ALIGNMENT, 1); // safe alignment for glReadPixels - // Try reading back from the back buffer first - glReadBuffer(GL_BACK); + // Ensure we're bound to the default framebuffer because the renderer may have left an FBO bound + glBindFramebuffer(GL_FRAMEBUFFER, 0); + CheckGLError("glBindFramebuffer(0)"); + + // Pick a read buffer that actually exists (GL_BACK if double-buffered; otherwise GL_FRONT) + int doubleBuffered = 0; + SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &doubleBuffered); + glReadBuffer(doubleBuffered ? GL_BACK : GL_FRONT); + CheckGLError("glReadBuffer"); + + // Read pixels from the chosen buffer glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); glFinish(); + CheckGLError("glReadPixels"); // Count non black pixels in back buffer size_t nonBlack = CountNonBlackPixels(pixels); @@ -86,8 +128,4 @@ TEST(RenderSmokeTest, AppRendersSomething) { if (threshold < 100) threshold = 100; EXPECT_GT(nonBlack, threshold); - - // Clean up - renderer.uninitialize(); - window.uninitialize(); } \ No newline at end of file From 80e97e0abd0049ac33c7209fad79b5aa00e25e00 Mon Sep 17 00:00:00 2001 From: riley-1995 Date: Tue, 24 Feb 2026 14:29:52 -0800 Subject: [PATCH 15/20] Update render test OpenGL import for Apple, silence depreciation warnings. --- tests/renderer/RenderSmokeTest.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/renderer/RenderSmokeTest.cpp b/tests/renderer/RenderSmokeTest.cpp index c9420ec5..75a3cb00 100644 --- a/tests/renderer/RenderSmokeTest.cpp +++ b/tests/renderer/RenderSmokeTest.cpp @@ -7,6 +7,11 @@ #include #endif +#ifdef __APPLE__ + #define GL_SILENCE_DEPRECATION + #include +#endif + #include #include #include From 13f2c42174f630f16968396250960cc5e893efb5 Mon Sep 17 00:00:00 2001 From: riley-1995 Date: Tue, 24 Feb 2026 17:17:16 -0800 Subject: [PATCH 16/20] Add integration test for CLI --help exit behavior --- tests/CMakeLists.txt | 3 +- tests/cli/CMakeLists.txt | 22 ++++++ tests/cli/HelpOptionTest.cpp | 142 +++++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 tests/cli/CMakeLists.txt create mode 100644 tests/cli/HelpOptionTest.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 57168054..62484078 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -67,4 +67,5 @@ target_link_libraries(projectMSDL_testcorelib # (To be kept in this file after integration of above code into src/CMakeLists.txt) # ====== add_subdirectory(renderer) -add_subdirectory(audio) \ No newline at end of file +add_subdirectory(audio) +add_subdirectory(cli) \ No newline at end of file diff --git a/tests/cli/CMakeLists.txt b/tests/cli/CMakeLists.txt new file mode 100644 index 00000000..4e670b2c --- /dev/null +++ b/tests/cli/CMakeLists.txt @@ -0,0 +1,22 @@ +find_package(GTest 1.10 REQUIRED NO_MODULE) + +add_executable(projectMSDL-cli-test + HelpOptionTest.cpp +) + +target_link_libraries(projectMSDL-cli-test + PRIVATE + GTest::gtest_main + Poco::Foundation +) + +# Ensure the app is built before the test runs +add_dependencies(projectMSDL-cli-test projectMSDL) + +# Pass the built binary path into the test as a compile definition +target_compile_definitions(projectMSDL-cli-test + PRIVATE + PROJECTMSDL_BINARY_PATH="$" +) + +add_test(NAME projectMSDL-cli-test COMMAND $) \ No newline at end of file diff --git a/tests/cli/HelpOptionTest.cpp b/tests/cli/HelpOptionTest.cpp new file mode 100644 index 00000000..10096163 --- /dev/null +++ b/tests/cli/HelpOptionTest.cpp @@ -0,0 +1,142 @@ +// HelpOptionTest.cpp +// +// CLI integration test: verifies `projectMSDL --help` prints usage/help text +// and exits successfully without entering ProjectMSDLApplication::main() +// or starting RenderLoop::Run(). + +#include + +// Poco: process launching, pipes, timing, filesystem +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifndef PROJECTMSDL_BINARY_PATH +#error "PROJECTMSDL_BINARY_PATH is not defined. Check tests/cli/CMakeLists.txt." +#endif + +// Read entire contents of a pipe into a string. +static std::string ReadAll(Poco::Pipe& pipe) +{ + Poco::PipeInputStream is(pipe); + std::ostringstream oss; + Poco::StreamCopier::copyStream(is, oss); + return oss.str(); +} + +// Result of running the CLI. +struct CliResult +{ + int exitCode; + std::string output; // stdout + stderr combined +}; + +// Helper: launches projectMSDL with given args. +// Throws std::runtime_error on failure. +static CliResult RunCli(const std::vector& args, + int timeoutSeconds = 3) +{ + const std::string binaryPath = PROJECTMSDL_BINARY_PATH; + + // Ensure binary exists and is executable. + Poco::File binFile(binaryPath); + if (!binFile.exists()) + throw std::runtime_error("Binary not found: " + binaryPath); + if (!binFile.canExecute()) + throw std::runtime_error("Binary not executable: " + binaryPath); + + // Capture stdout/stderr. + Poco::Pipe outPipe; + Poco::Pipe errPipe; + + // Launch the process and construct the ProcessHandle from the return value. + Poco::ProcessHandle handle = + Poco::Process::launch(binaryPath, args, nullptr, &outPipe, &errPipe); + + // --- Timeout guard (detect accidental RenderLoop entry) --- + const Poco::Timespan timeout(timeoutSeconds, 0); + Poco::Timestamp start; + int exitCode = -1; + + while (true) + { + const int maybeExit = handle.tryWait(); // -1 if still running + if (maybeExit != -1) + { + exitCode = maybeExit; + break; + } + + if (start.elapsed() > timeout.totalMicroseconds()) + { + try { Poco::Process::kill(handle); } catch (...) {} + throw std::runtime_error( + "CLI process did not exit before timeout " + "(possible RenderLoop entry)."); + } + + Poco::Thread::sleep(10); + } + + // Read output after exit. + std::string stdoutText; + std::string stderrText; + + try { stdoutText = ReadAll(outPipe); } + catch (const Poco::Exception& ex) + { + throw std::runtime_error( + std::string("Error reading stdout: ") + ex.displayText()); + } + + try { stderrText = ReadAll(errPipe); } + catch (const Poco::Exception& ex) + { + throw std::runtime_error( + std::string("Error reading stderr: ") + ex.displayText()); + } + + return { exitCode, stdoutText + (stderrText.empty() ? "" : "\n" + stderrText) }; +} + +TEST(CliHelpOption, HelpPrintsAndExitsWithoutRunning) +{ + CliResult result; + + try + { + result = RunCli({ "--help" }); + } + catch (const std::exception& ex) + { + FAIL() << "RunCli failed: " << ex.what(); + return; + } + + // Must exit successfully. + ASSERT_EQ(result.exitCode, 0) + << "Non-zero exit (" << result.exitCode << "). Output:\n" + << result.output; + + // Must print recognizable help/usage text. + EXPECT_NE(result.output.find("projectM SDL Standalone Visualizer"), + std::string::npos) + << "Expected help header not found.\nOutput:\n" + << result.output; + + EXPECT_NE(result.output.find("[options]"), + std::string::npos) + << "Expected usage/options text not found.\nOutput:\n" + << result.output; +} \ No newline at end of file From f96e156b9564b48f95e63677096f5422a83ab510 Mon Sep 17 00:00:00 2001 From: tooth-juice Date: Tue, 24 Feb 2026 18:45:47 -0800 Subject: [PATCH 17/20] Added support for windows specific help option --- tests/cli/HelpOptionTest.cpp | 41 ++++++++++++------------------------ 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/tests/cli/HelpOptionTest.cpp b/tests/cli/HelpOptionTest.cpp index 10096163..d66e37d8 100644 --- a/tests/cli/HelpOptionTest.cpp +++ b/tests/cli/HelpOptionTest.cpp @@ -44,25 +44,21 @@ struct CliResult // Helper: launches projectMSDL with given args. // Throws std::runtime_error on failure. -static CliResult RunCli(const std::vector& args, - int timeoutSeconds = 3) +static CliResult RunCli(const std::vector& args, int timeoutSeconds = 3) { const std::string binaryPath = PROJECTMSDL_BINARY_PATH; // Ensure binary exists and is executable. Poco::File binFile(binaryPath); - if (!binFile.exists()) - throw std::runtime_error("Binary not found: " + binaryPath); - if (!binFile.canExecute()) - throw std::runtime_error("Binary not executable: " + binaryPath); + if (!binFile.exists()) throw std::runtime_error("Binary not found: " + binaryPath); + if (!binFile.canExecute()) throw std::runtime_error("Binary not executable: " + binaryPath); // Capture stdout/stderr. Poco::Pipe outPipe; Poco::Pipe errPipe; // Launch the process and construct the ProcessHandle from the return value. - Poco::ProcessHandle handle = - Poco::Process::launch(binaryPath, args, nullptr, &outPipe, &errPipe); + Poco::ProcessHandle handle = Poco::Process::launch(binaryPath, args, nullptr, &outPipe, &errPipe); // --- Timeout guard (detect accidental RenderLoop entry) --- const Poco::Timespan timeout(timeoutSeconds, 0); @@ -81,9 +77,7 @@ static CliResult RunCli(const std::vector& args, if (start.elapsed() > timeout.totalMicroseconds()) { try { Poco::Process::kill(handle); } catch (...) {} - throw std::runtime_error( - "CLI process did not exit before timeout " - "(possible RenderLoop entry)."); + throw std::runtime_error("CLI process did not exit before timeout (possible RenderLoop entry)."); } Poco::Thread::sleep(10); @@ -96,15 +90,13 @@ static CliResult RunCli(const std::vector& args, try { stdoutText = ReadAll(outPipe); } catch (const Poco::Exception& ex) { - throw std::runtime_error( - std::string("Error reading stdout: ") + ex.displayText()); + throw std::runtime_error(std::string("Error reading stdout: ") + ex.displayText()); } try { stderrText = ReadAll(errPipe); } catch (const Poco::Exception& ex) { - throw std::runtime_error( - std::string("Error reading stderr: ") + ex.displayText()); + throw std::runtime_error(std::string("Error reading stderr: ") + ex.displayText()); } return { exitCode, stdoutText + (stderrText.empty() ? "" : "\n" + stderrText) }; @@ -116,7 +108,11 @@ TEST(CliHelpOption, HelpPrintsAndExitsWithoutRunning) try { +#ifdef _WIN32 + result = RunCli({ "/help" }); +#else result = RunCli({ "--help" }); +#endif } catch (const std::exception& ex) { @@ -125,18 +121,9 @@ TEST(CliHelpOption, HelpPrintsAndExitsWithoutRunning) } // Must exit successfully. - ASSERT_EQ(result.exitCode, 0) - << "Non-zero exit (" << result.exitCode << "). Output:\n" - << result.output; + ASSERT_EQ(result.exitCode, 0) << "Non-zero exit (" << result.exitCode << "). Output:\n" << result.output; // Must print recognizable help/usage text. - EXPECT_NE(result.output.find("projectM SDL Standalone Visualizer"), - std::string::npos) - << "Expected help header not found.\nOutput:\n" - << result.output; - - EXPECT_NE(result.output.find("[options]"), - std::string::npos) - << "Expected usage/options text not found.\nOutput:\n" - << result.output; + EXPECT_NE(result.output.find("projectM SDL Standalone Visualizer"), std::string::npos) << "Expected help header not found.\nOutput:\n" << result.output; + EXPECT_NE(result.output.find("[options]"), std::string::npos) << "Expected usage/options text not found.\nOutput:\n" << result.output; } \ No newline at end of file From d728cf3d2eb83ceb410525f46aef25b892d87b1a Mon Sep 17 00:00:00 2001 From: tooth-juice Date: Tue, 24 Feb 2026 20:02:34 -0800 Subject: [PATCH 18/20] Added unit test for issue 2; Modified CMAKE files for issue 2 --- tests/CMakeLists.txt | 60 ++++++++++++++++++++++++++++ tests/renderer/CMakeLists.txt | 23 ++++++++++- tests/renderer/MainExecutionTest.cpp | 56 ++++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 tests/renderer/MainExecutionTest.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 62484078..5aadec6c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -61,6 +61,66 @@ target_link_libraries(projectMSDL_testcorelib libprojectM::playlist ) +# ====== +# Test-only core library WITHOUT RenderLoop +# ====== + +# Build a core library from production sources to test on +add_library(projectMSDL_testcorelib_norenderloop + # Add .cpp files as needed for testing + # Examples: + # ${PROJECT_SOURCE_DIR}/src/SDLRenderingWindow.cpp + # ${PROJECT_SOURCE_DIR}/src/RenderLoop.cpp + # ${PROJECT_SOURCE_DIR}/src/ProjectMWrapper.cpp + # For now, we'll include a simple file just so this thing works. It can be removed if unneeded later. + ${PROJECT_SOURCE_DIR}/src/FPSLimiter.cpp + ${PROJECT_SOURCE_DIR}/src/ProjectMSDLApplication.cpp + ${PROJECT_SOURCE_DIR}/src/ProjectMWrapper.cpp + ${PROJECT_SOURCE_DIR}/src/SDLRenderingWindow.cpp + ${PROJECT_SOURCE_DIR}/src/AudioCapture.cpp +) + +if (WIN32) + target_sources(projectMSDL_testcorelib_norenderloop PRIVATE + ${PROJECT_SOURCE_DIR}/src/AudioCaptureImpl_WASAPI.cpp + ) + target_compile_definitions(projectMSDL_testcorelib_norenderloop PRIVATE + AUDIO_IMPL_HEADER="AudioCaptureImpl_WASAPI.h" + USE_GLEW + ) +else() + target_sources(projectMSDL_testcorelib_norenderloop PRIVATE + ${PROJECT_SOURCE_DIR}/src/AudioCaptureImpl_SDL.cpp + ) + target_compile_definitions(projectMSDL_testcorelib_norenderloop PRIVATE + AUDIO_IMPL_HEADER="AudioCaptureImpl_SDL.h" + ) +endif() + +# Headers from the production source tree +target_include_directories(projectMSDL_testcorelib_norenderloop + PUBLIC + ${PROJECT_SOURCE_DIR}/src +) + +# Compile definitions that are commonly referenced +target_compile_definitions(projectMSDL_testcorelib_norenderloop + PRIVATE + PROJECTMSDL_CONFIG_LOCATION="${DEFAULT_CONFIG_PATH}" + PROJECTMSDL_VERSION="${PROJECT_VERSION}" +) + +# Linker set (add to this only when the linker asks) +target_link_libraries(projectMSDL_testcorelib_norenderloop + PRIVATE + SDL2::SDL2 + Poco::Util + Poco::Foundation + ProjectMSDL-GUI + ProjectMSDL-Notifications + libprojectM::playlist +) + # ====== # Actual test subdirs # Add to this when defining more testing directories diff --git a/tests/renderer/CMakeLists.txt b/tests/renderer/CMakeLists.txt index 7a51ba0e..1f4cf9ae 100644 --- a/tests/renderer/CMakeLists.txt +++ b/tests/renderer/CMakeLists.txt @@ -1,6 +1,9 @@ # Requires gtest, otherwise fail find_package(GTest 1.10 REQUIRED NO_MODULE) +# === +# Integration Test +# === # Creation of executable for this specific test add_executable(projectMSDL-renderer-test RenderSmokeTest.cpp @@ -15,4 +18,22 @@ target_link_libraries(projectMSDL-renderer-test ) # Add our test to the executable -add_test(NAME projectMSDL-renderer-test COMMAND projectMSDL-renderer-test) \ No newline at end of file +add_test(NAME projectMSDL-renderer-test COMMAND projectMSDL-renderer-test) + + +# === +# Unit Test +# === +# Unit test executable (fake RenderLoop via symbols in MainExecutionTest.cpp) +add_executable(projectMSDL-mainexec-test + MainExecutionTest.cpp +) + +target_link_libraries(projectMSDL-mainexec-test + PRIVATE + projectMSDL_testcorelib_norenderloop + GTest::gtest + GTest::gtest_main +) + +add_test(NAME projectMSDL-mainexec-test COMMAND projectMSDL-mainexec-test) \ No newline at end of file diff --git a/tests/renderer/MainExecutionTest.cpp b/tests/renderer/MainExecutionTest.cpp new file mode 100644 index 00000000..ac39fff0 --- /dev/null +++ b/tests/renderer/MainExecutionTest.cpp @@ -0,0 +1,56 @@ +#define SDL_MAIN_HANDLED 1 + +#include + +#include +#include +#include + +#include +#include + +#include "ProjectMSDLApplication.h" +#include "RenderLoop.h" + +#include "AudioCapture.h" +#include "ProjectMWrapper.h" +#include "SDLRenderingWindow.h" +#include "gui/ProjectMGUI.h" +#include "notifications/QuitNotification.h" + +static int g_run_calls = 0; + +// RenderLoop.cpp is excluded from projectMSDL_testcorelib_norenderloop so we provide these symbols here. +RenderLoop::RenderLoop() + : _audioCapture(Poco::Util::Application::instance().getSubsystem()) + , _projectMWrapper(Poco::Util::Application::instance().getSubsystem()) + , _sdlRenderingWindow(Poco::Util::Application::instance().getSubsystem()) + , _projectMGui(Poco::Util::Application::instance().getSubsystem()) +{ +} + +// RenderLoop has an observer bound to this method. +void RenderLoop::QuitNotificationHandler(const Poco::AutoPtr&) +{ +} + +void RenderLoop::Run() +{ + ++g_run_calls; +} + +class TestableProjectMSDLApplication final : public ProjectMSDLApplication { +public: + using ProjectMSDLApplication::main; +}; + +TEST(ProjectMSDLApplicationMain, CallsRenderLoopRunExactlyOnceAndReturnsExitSuccess) +{ + g_run_calls = 0; + + TestableProjectMSDLApplication app; + const int rc = app.main(std::vector{}); + + EXPECT_EQ(g_run_calls, 1); + EXPECT_EQ(rc, EXIT_SUCCESS); +} \ No newline at end of file From 375dc0bfe3c9257cce183b86ba12b851b4db574d Mon Sep 17 00:00:00 2001 From: riley-1995 Date: Thu, 26 Feb 2026 15:22:51 -0800 Subject: [PATCH 19/20] Add Esc Overlay Toggle Test --- tests/CMakeLists.txt | 3 +- tests/ui/CMakeLists.txt | 18 +++ tests/ui/EscOverlayToggleTest.cpp | 213 ++++++++++++++++++++++++++++++ 3 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 tests/ui/CMakeLists.txt create mode 100644 tests/ui/EscOverlayToggleTest.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5aadec6c..301dbe10 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -128,4 +128,5 @@ target_link_libraries(projectMSDL_testcorelib_norenderloop # ====== add_subdirectory(renderer) add_subdirectory(audio) -add_subdirectory(cli) \ No newline at end of file +add_subdirectory(cli) +add_subdirectory(ui) \ No newline at end of file diff --git a/tests/ui/CMakeLists.txt b/tests/ui/CMakeLists.txt new file mode 100644 index 00000000..8335f6ac --- /dev/null +++ b/tests/ui/CMakeLists.txt @@ -0,0 +1,18 @@ +# Requires gtest, otherwise fail +find_package(GTest 1.10 REQUIRED NO_MODULE) + +# Creation of executable for this specific test +add_executable(projectMSDL-ui-test + EscOverlayToggleTest.cpp +) + +# Call required libraries +target_link_libraries(projectMSDL-ui-test + PRIVATE + ImGui + projectMSDL_testcorelib + GTest::gtest_main +) + +# Add our test to the executable +add_test(NAME projectMSDL-ui-test COMMAND projectMSDL-ui-test) \ No newline at end of file diff --git a/tests/ui/EscOverlayToggleTest.cpp b/tests/ui/EscOverlayToggleTest.cpp new file mode 100644 index 00000000..3e99c68c --- /dev/null +++ b/tests/ui/EscOverlayToggleTest.cpp @@ -0,0 +1,213 @@ +// tests/ui/EscOverlayToggleTest.cpp + +#include + +#include "ProjectMSDLApplication.h" +#include "RenderLoop.h" +#include "SDLRenderingWindow.h" +#include "ProjectMWrapper.h" +#include "gui/ProjectMGUI.h" + +#include +#include + +#include +#include +#include + +namespace { + +#if !defined(_WIN32) +void ConfigureHeadlessIfNeeded() +{ +#if defined(__linux__) + const char* display = std::getenv("DISPLAY"); + if (!display || display[0] == '\0') + { + if (!std::getenv("SDL_VIDEODRIVER")) + setenv("SDL_VIDEODRIVER", "dummy", 1); + } +#endif + if (!std::getenv("SDL_AUDIODRIVER")) + setenv("SDL_AUDIODRIVER", "dummy", 1); +} +#endif + +class RenderLoopTestHarness : public RenderLoop +{ +public: + void PollEventsPublic() { PollEvents(); } + bool WantsToQuitPublic() const { return _wantsToQuit; } +}; + +void PumpOnce(ProjectMGUI& gui, RenderLoopTestHarness& loop) +{ + loop.PollEventsPublic(); + gui.Draw(); +} + +void PushEscPress(SDL_Window* w) +{ + const Uint32 wid = w ? SDL_GetWindowID(w) : 0; + + SDL_Event e{}; + e.type = SDL_KEYDOWN; + e.key.type = SDL_KEYDOWN; + e.key.windowID = wid; + e.key.state = SDL_PRESSED; + e.key.repeat = 0; + e.key.keysym.sym = SDLK_ESCAPE; + e.key.keysym.scancode = SDL_SCANCODE_ESCAPE; + e.key.keysym.mod = KMOD_NONE; + ASSERT_EQ(SDL_PushEvent(&e), 1); + + e = SDL_Event{}; + e.type = SDL_KEYUP; + e.key.type = SDL_KEYUP; + e.key.windowID = wid; + e.key.state = SDL_RELEASED; + e.key.repeat = 0; + e.key.keysym.sym = SDLK_ESCAPE; + e.key.keysym.scancode = SDL_SCANCODE_ESCAPE; + e.key.keysym.mod = KMOD_NONE; + ASSERT_EQ(SDL_PushEvent(&e), 1); +} + +void PushBackgroundClick(SDL_Window* w, int x, int y) +{ + if (!w) return; + const Uint32 wid = SDL_GetWindowID(w); + + SDL_Event e{}; + e.type = SDL_MOUSEMOTION; + e.motion.windowID = wid; + e.motion.which = 0; + e.motion.x = x; + e.motion.y = y; + e.motion.xrel = 0; + e.motion.yrel = 0; + SDL_PushEvent(&e); + + e = SDL_Event{}; + e.type = SDL_MOUSEBUTTONDOWN; + e.button.windowID = wid; + e.button.which = 0; + e.button.button = SDL_BUTTON_LEFT; + e.button.state = SDL_PRESSED; + e.button.clicks = 1; + e.button.x = x; + e.button.y = y; + SDL_PushEvent(&e); + + e = SDL_Event{}; + e.type = SDL_MOUSEBUTTONUP; + e.button.windowID = wid; + e.button.which = 0; + e.button.button = SDL_BUTTON_LEFT; + e.button.state = SDL_RELEASED; + e.button.clicks = 1; + e.button.x = x; + e.button.y = y; + SDL_PushEvent(&e); +} + +bool WaitForImGuiReady(ProjectMGUI& gui, RenderLoopTestHarness& loop, int maxFrames) +{ + for (int i = 0; i < maxFrames; ++i) + { + PumpOnce(gui, loop); + if (ImGui::GetCurrentContext() != nullptr) + return true; + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + } + return false; +} + +bool WaitForImGuiNotCapturingKeyboard(ProjectMGUI& gui, RenderLoopTestHarness& loop, int maxFrames) +{ + for (int i = 0; i < maxFrames; ++i) + { + PumpOnce(gui, loop); + + if (ImGui::GetCurrentContext() == nullptr) + { + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + continue; + } + + const ImGuiIO& io = ImGui::GetIO(); + if (!io.WantCaptureKeyboard) + return true; + + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + } + return false; +} + +} // namespace + +TEST(UIToggleOverlayTest, EscTogglesOverlayVisibilityOncePerPress) +{ +#if !defined(_WIN32) + ConfigureHeadlessIfNeeded(); +#endif + + const char* argv0 = "projectMSDL-ui-test"; + int argc = 1; + char* argv[] = { const_cast(argv0), nullptr }; + + ProjectMSDLApplication app; + ASSERT_NO_THROW(app.init(argc, argv)); + + auto& window = app.getSubsystem(); + auto& renderer = app.getSubsystem(); + auto& gui = app.getSubsystem(); + + ASSERT_NO_THROW(window.initialize(app)); + ASSERT_NO_THROW(renderer.initialize(app)); + ASSERT_NO_THROW(gui.initialize(app)); + + SDL_Window* sdlWin = window.GetRenderingWindow(); + ASSERT_NE(sdlWin, nullptr); + + RenderLoopTestHarness loop; + + ASSERT_TRUE(WaitForImGuiReady(gui, loop, 60)) << "ImGui context never became ready."; + + // Try to ensure no widget is focused/active by clicking the far bottom-right. + int w = 0, h = 0; + SDL_GetWindowSize(sdlWin, &w, &h); + PushBackgroundClick(sdlWin, (w > 0 ? w - 2 : 1), (h > 0 ? h - 2 : 1)); + + // Wait until ImGui naturally reports it is NOT capturing keyboard. + ASSERT_TRUE(WaitForImGuiNotCapturingKeyboard(gui, loop, 120)) + << "ImGui kept WantCaptureKeyboard=true. " + << "This can happen depending on UI state; test may be flaky on some platforms."; + + const bool initialVisible = gui.Visible(); + + // Press ESC once -> should flip + PushEscPress(sdlWin); + PumpOnce(gui, loop); + + EXPECT_EQ(gui.Visible(), !initialVisible); + EXPECT_FALSE(loop.WantsToQuitPublic()); + + // Again: click background + wait for no-capture before the second press + SDL_GetWindowSize(sdlWin, &w, &h); + PushBackgroundClick(sdlWin, (w > 0 ? w - 2 : 1), (h > 0 ? h - 2 : 1)); + + ASSERT_TRUE(WaitForImGuiNotCapturingKeyboard(gui, loop, 120)) + << "ImGui kept WantCaptureKeyboard=true before second ESC press."; + + // Press ESC again -> should restore + PushEscPress(sdlWin); + PumpOnce(gui, loop); + + EXPECT_EQ(gui.Visible(), initialVisible); + EXPECT_FALSE(loop.WantsToQuitPublic()); + + gui.uninitialize(); + renderer.uninitialize(); + window.uninitialize(); +} \ No newline at end of file From ed587555fd118b3b2a2a30a6078227bdb3b0f419 Mon Sep 17 00:00:00 2001 From: riley-1995 Date: Thu, 26 Feb 2026 15:37:08 -0800 Subject: [PATCH 20/20] cmake: fix macOS link by adding OpenGL to projectMSDL --- src/CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ba764dcd..b48fc850 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,8 @@ set(PROJECTM_CONFIGURATION_FILE "${CMAKE_CURRENT_BINARY_DIR}/projectMSDL.propert set(PROJECTM_CONFIGURATION_FILE "${PROJECTM_CONFIGURATION_FILE}" PARENT_SCOPE) configure_file(resources/projectMSDL.properties.in "${PROJECTM_CONFIGURATION_FILE}" @ONLY) +find_package(OpenGL REQUIRED) + add_executable(projectMSDL WIN32 MACOSX_BUNDLE AudioCapture.cpp AudioCapture.h @@ -82,6 +84,14 @@ target_link_libraries(projectMSDL SDL2::SDL2main ) +if (TARGET OpenGL::GL) + target_link_libraries(projectMSDL PRIVATE OpenGL::GL) +elseif (TARGET OpenGL::OpenGL) + target_link_libraries(projectMSDL PRIVATE OpenGL::OpenGL) +else() + message(FATAL_ERROR "No OpenGL CMake target found.") +endif() + if (MSVC) set_target_properties(projectMSDL PROPERTIES