Create unit-test windows hidden from the start while preserving hardware OpenGL rendering. Force double buffering for hidden X11 windows so framebuffer capture reads the rendered back buffer reliably, and keep SDL2/SDL3 behavior consistent. Update unit-test documentation to use direct desktop execution by default, with Xvfb retained for headless environments.

This change might break unit-tests in other platforms, next CI run will let us know.
This commit is contained in:
Martín Lucas Golini
2026-08-22 14:11:11 -03:00
parent 405e84608f
commit 681841e11d
9 changed files with 61 additions and 22 deletions
+24 -14
View File
@@ -7,23 +7,33 @@ The test binary manages its own current working directory, so you can execute it
* **Prefer the release test binary during normal development:**
When AddressSanitizer or other debug-only diagnostics are not required, build and run `bin/unit_tests/eepp-unit_tests`. The optimized release suite is substantially faster and should be the default for iterative testing. Use `bin/unit_tests/eepp-unit_tests-debug` when investigating memory safety, assertions, or other behavior that specifically requires the debug configuration.
* **Default Execution for Agents on Linux & FreeBSD:**
Always run unit tests through the project wrapper unless the user explicitly asks for a different harness:
`projects/scripts/xvfb-run-eepp bin/unit_tests/eepp-unit_tests`
* **Why the wrapper is required:**
Tests open ~400 individual windows. The wrapper runs them in an isolated framebuffer, enables race-safe automatic display selection for concurrent agent test runs, sets the default screen to `1280x1024x24`, and injects `ASAN_OPTIONS=detect_leaks=0` automatically.
* **Do not skip the wrapper for filtered tests:**
A focused test still needs the same wrapper:
`projects/scripts/xvfb-run-eepp bin/unit_tests/eepp-unit_tests --filter="FontRendering.*Offset*"`
* **Fallback only when the wrapper itself fails:**
If `projects/scripts/xvfb-run-eepp` fails before launching the test binary, report that wrapper failure and then use this fallback to keep verification moving:
`xvfb-run -a -s "-screen 0 1280x1024x24" bin/unit_tests/eepp-unit_tests`
Do not use plain `xvfb-run` as the first attempt for GUI/unit tests.
* **Direct Execution (Only for non-window tests or explicit user requests):**
* **Default Execution on a Graphical Linux Desktop:**
Unit-test windows are created hidden, so run the release suite directly against the desktop:
`bin/unit_tests/eepp-unit_tests`
This keeps the windows invisible and unfocused while preserving hardware OpenGL acceleration.
Confirm that the renderer log names the real GPU rather than llvmpipe when validating rendering
behavior or performance.
* **Filtered Tests on a Graphical Linux Desktop:**
Use the same direct hardware-backed command for focused runs:
`bin/unit_tests/eepp-unit_tests --filter="FontRendering.*Offset*"`
* **Headless CI and Systems Without a Usable Desktop Display:**
Keep `projects/scripts/xvfb-run-eepp` as the fallback when no desktop display is available:
`projects/scripts/xvfb-run-eepp bin/unit_tests/eepp-unit_tests`
The wrapper provides a race-safe isolated display at `1280x1024x24` and injects
`ASAN_OPTIONS=detect_leaks=0`. Xvfb can use llvmpipe, so do not use wrapper timings to evaluate
hardware-rendered performance.
* **Wrapper Fallback:**
If `projects/scripts/xvfb-run-eepp` itself fails before launching the test binary, report that
failure and use:
`xvfb-run -a -s "-screen 0 1280x1024x24" bin/unit_tests/eepp-unit_tests`
Do not use plain `xvfb-run` as the first headless attempt.
* **Non-window Tests:**
Tests known not to create windows can run without selecting a video driver:
`bin/unit_tests/eepp-unit_tests --filter="NonWindowTest.*"`
* **Filtering Tests:**
Use the `--filter` parameter to run specific tests (supports glob patterns).
Keep the wrapper in front of the binary unless the test is known not to create windows.
Keep the same harness as the full suite: direct execution on a graphical Linux desktop, or the
wrapper in a genuinely headless environment.
## Writing New Tests
Writing new tests is highly encouraged, but depends on the context of your changes:
+3 -3
View File
@@ -4,10 +4,10 @@
#include <eepp/config.hpp>
#include <string>
#define EEPP_MAJOR_VERSION 3
#define EEPP_MINOR_VERSION 1
#define EEPP_MAJOR_VERSION 4
#define EEPP_MINOR_VERSION 0
#define EEPP_PATCH_LEVEL 0
#define EEPP_CODENAME "Khaya"
#define EEPP_CODENAME "Asādhāraṇa"
/** The compiled version of the library */
#define EEPP_VERSION( x ) \
+3
View File
@@ -25,8 +25,11 @@ enum WindowStyle {
Resize = ( 1 << 2 ),
Fullscreen = ( 1 << 3 ),
UseDesktopResolution = ( 1 << 4 ),
Hidden = ( 1 << 5 ),
#if EE_PLATFORM == EE_PLATFORM_IOS || EE_PLATFORM == EE_PLATFORM_ANDROID
Default = Borderless
#elif defined( EE_UNIT_TESTS )
Default = Titlebar | Resize | Hidden
#else
Default = Titlebar | Resize
#endif
+1
View File
@@ -1954,6 +1954,7 @@ solution "eepp"
project "eepp-unit_tests"
kind "ConsoleApp"
targetdir("./bin/unit_tests")
defines { "EE_UNIT_TESTS" }
links { "eterm-static", "languages-syntax-highlighting-static" }
includedirs { "src/modules/eterm/include/", "src/thirdparty" }
language "C++"
+1
View File
@@ -1982,6 +1982,7 @@ workspace "eepp"
project "eepp-unit_tests"
kind "ConsoleApp"
targetdir(_MAIN_SCRIPT_DIR .. "/bin/unit_tests")
defines { "EE_UNIT_TESTS" }
links { "eterm-static", "languages-syntax-highlighting-static" }
incdirs { "src/modules/eterm/include/", "src/thirdparty" }
language "C++"
+13 -1
View File
@@ -84,6 +84,13 @@ bool WindowSDL::create( WindowSettings Settings, ContextSettings Context ) {
mWindow.WindowConfig = Settings;
mWindow.ContextConfig = Context;
#if defined( EE_X11_PLATFORM )
// Unmapped GLX windows do not provide reliable front-buffer storage. Keep hidden windows
// double-buffered so rendering and readback use the drawable's back buffer.
if ( mWindow.WindowConfig.Style & WindowStyle::Hidden )
mWindow.ContextConfig.DoubleBuffering = true;
#endif
if ( !SDL_WasInit( SDL_INIT_VIDEO ) && SDL_Init( SDL_INIT_VIDEO ) != 0 ) {
Log::error( "Unable to initialize SDL: %s", SDL_GetError() );
@@ -106,7 +113,9 @@ bool WindowSDL::create( WindowSettings Settings, ContextSettings Context ) {
mWindow.WindowConfig.Height = mWindow.DesktopResolution.getHeight();
}
mWindow.Flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN |
mWindow.Flags = SDL_WINDOW_OPENGL |
( ( mWindow.WindowConfig.Style & WindowStyle::Hidden ) ? SDL_WINDOW_HIDDEN
: SDL_WINDOW_SHOWN ) |
( ( !mWindow.WindowConfig.DisableHiDPI ? SDL_WINDOW_ALLOW_HIGHDPI : 0 ) );
if ( mWindow.WindowConfig.Style & WindowStyle::Resize ) {
@@ -284,6 +293,9 @@ bool WindowSDL::create( WindowSettings Settings, ContextSettings Context ) {
Backend::BackendHelper::setUserTheme( (HWND)getWindowHandler() );
#endif
if ( mWindow.WindowConfig.Style & WindowStyle::Hidden )
SDL_HideWindow( mSDLWindow );
logSuccessfulInit( getVersion() );
return true;
+14 -2
View File
@@ -69,6 +69,13 @@ bool WindowSDL::create( WindowSettings Settings, ContextSettings Context ) {
mWindow.WindowConfig = Settings;
mWindow.ContextConfig = Context;
#if defined( EE_X11_PLATFORM )
// Unmapped GLX windows do not provide reliable front-buffer storage. Keep hidden windows
// double-buffered so rendering and readback use the drawable's back buffer.
if ( mWindow.WindowConfig.Style & WindowStyle::Hidden )
mWindow.ContextConfig.DoubleBuffering = true;
#endif
if ( !SDL_WasInit( SDL_INIT_VIDEO ) && !SDL_Init( SDL_INIT_VIDEO ) ) {
Log::error( "Unable to initialize SDL: %s", SDL_GetError() );
@@ -96,8 +103,10 @@ bool WindowSDL::create( WindowSettings Settings, ContextSettings Context ) {
mWindow.WindowConfig.Height = mWindow.DesktopResolution.getHeight();
}
mWindow.Flags = SDL_WINDOW_OPENGL |
( ( !mWindow.WindowConfig.DisableHiDPI ? SDL_WINDOW_HIGH_PIXEL_DENSITY : 0 ) );
mWindow.Flags =
SDL_WINDOW_OPENGL |
( ( mWindow.WindowConfig.Style & WindowStyle::Hidden ) ? SDL_WINDOW_HIDDEN : 0 ) |
( ( !mWindow.WindowConfig.DisableHiDPI ? SDL_WINDOW_HIGH_PIXEL_DENSITY : 0 ) );
if ( mWindow.WindowConfig.Style & WindowStyle::Resize ) {
mWindow.Flags |= SDL_WINDOW_RESIZABLE;
@@ -259,6 +268,9 @@ bool WindowSDL::create( WindowSettings Settings, ContextSettings Context ) {
Backend::BackendHelper::setUserTheme( (HWND)getWindowHandler() );
#endif
if ( mWindow.WindowConfig.Style & WindowStyle::Hidden )
SDL_HideWindow( mSDLWindow );
logSuccessfulInit( getVersion() );
return true;
+1 -1
View File
@@ -14,7 +14,7 @@ using namespace EE::System;
using namespace EE::Graphics;
using namespace EE::Window;
static constexpr Uint32 VisualTestWindowStyle = WindowStyle::Borderless;
static constexpr Uint32 VisualTestWindowStyle = WindowStyle::Borderless | WindowStyle::Hidden;
static void compareImages( utest_state_s& utest_state, int* utest_result, EE::Window::Window* win,
const std::string& imageName,
+1 -1
View File
@@ -8,7 +8,7 @@ using namespace EE;
#define ECODE_MAJOR_VERSION 0
#define ECODE_MINOR_VERSION 8
#define ECODE_PATCH_LEVEL 1
#define ECODE_PATCH_LEVEL 2
/* ECODE_COMMIT_NUMBER 9999 is used for official releases, nightly builds (pre-releases) will
* contain the number of commits after the last official release
*/