Skip to content

Commit

Permalink
ci/cd: Run integration tests without OpenGL (#9466)
Browse files Browse the repository at this point in the history
  • Loading branch information
quyykk committed Dec 15, 2023
1 parent 2d4e48e commit 4be1d8c
Show file tree
Hide file tree
Showing 12 changed files with 99 additions and 58 deletions.
18 changes: 13 additions & 5 deletions source/GameData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,11 @@ namespace {



future<void> GameData::BeginLoad(bool onlyLoadData, bool debugMode)
future<void> GameData::BeginLoad(bool onlyLoadData, bool debugMode, bool preventUpload)
{
if(preventUpload)
spriteQueue.SetPreventUpload();

// Initialize the list of "source" folders based on any active plugins.
LoadSources();

Expand Down Expand Up @@ -196,14 +199,19 @@ void GameData::CheckReferences()



void GameData::LoadShaders()
void GameData::LoadSettings()
{
FontSet::Add(Files::Images() + "font/ubuntu14r.png", 14);
FontSet::Add(Files::Images() + "font/ubuntu18r.png", 18);

// Load the key settings.
Command::LoadSettings(Files::Resources() + "keys.txt");
Command::LoadSettings(Files::Config() + "keys.txt");
}



void GameData::LoadShaders()
{
FontSet::Add(Files::Images() + "font/ubuntu14r.png", 14);
FontSet::Add(Files::Images() + "font/ubuntu18r.png", 18);

FillShader::Init();
FogShader::Init();
Expand Down
3 changes: 2 additions & 1 deletion source/GameData.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,11 @@ class Wormhole;
// universe.
class GameData {
public:
static std::future<void> BeginLoad(bool onlyLoadData, bool debugMode);
static std::future<void> BeginLoad(bool onlyLoadData, bool debugMode, bool preventUpload);
static void FinishLoading();
// Check for objects that are referred to but never defined.
static void CheckReferences();
static void LoadSettings();
static void LoadShaders();
static double GetProgress();
// Whether initial game loading is complete (data, sprites and audio are loaded).
Expand Down
15 changes: 14 additions & 1 deletion source/GameWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ string GameWindow::SDLVersions()



bool GameWindow::Init()
bool GameWindow::Init(bool headless)
{
#ifdef _WIN32
// Tell Windows this process is high dpi aware and doesn't need to get scaled.
Expand All @@ -80,6 +80,10 @@ bool GameWindow::Init()
setenv("SDL_VIDEO_X11_WMCLASS", "io.github.endless_sky.endless_sky", true);
#endif

// When running the integration tests, don't create a window nor an OpenGL context.
if(headless)
SDL_SetHint(SDL_HINT_VIDEODRIVER, "dummy");

// This needs to be called before any other SDL commands.
if(SDL_Init(SDL_INIT_VIDEO) != 0)
{
Expand Down Expand Up @@ -138,6 +142,15 @@ bool GameWindow::Init()
return false;
}

// Bail out early if we are in headless mode; no need to initialize all the OpenGL stuff.
if(headless)
{
width = windowWidth;
height = windowHeight;
Screen::SetRaw(width, height);
return true;
}

// Settings that must be declared before the context creation.
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
#ifdef _WIN32
Expand Down
2 changes: 1 addition & 1 deletion source/GameWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ this program. If not, see <https://www.gnu.org/licenses/>.
class GameWindow {
public:
static std::string SDLVersions();
static bool Init();
static bool Init(bool headless);
static void Quit();

// Paint the next frame in the main window.
Expand Down
10 changes: 8 additions & 2 deletions source/ImageSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,16 +322,22 @@ void ImageSet::Load() noexcept(false)



// Create the sprite and upload the image data to the GPU. After this is
// Create the sprite and optionally upload the image data to the GPU. After this is
// called, the internal image buffers and mask vector will be cleared, but
// the paths are saved in case the sprite needs to be loaded again.
void ImageSet::Upload(Sprite *sprite)
void ImageSet::Upload(Sprite *sprite, bool enableUpload)
{
// Clear all the buffers if we are not uploading the image data.
if(!enableUpload)
for(ImageBuffer &it : buffer)
it.Clear();

// Load the frames (this will clear the buffers).
sprite->AddFrames(buffer[0], false);
sprite->AddFrames(buffer[1], true);
sprite->AddSwizzleMaskFrames(buffer[2], false);
sprite->AddSwizzleMaskFrames(buffer[3], true);

GameData::GetMaskManager().SetMasks(sprite, std::move(masks));
masks.clear();
}
4 changes: 2 additions & 2 deletions source/ImageSet.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ class ImageSet {
// Load all the frames. This should be called in one of the image-loading
// worker threads. This also generates collision masks if needed.
void Load() noexcept(false);
// Create the sprite and upload the image data to the GPU. After this is
// Create the sprite and optionally upload the image data to the GPU. After this is
// called, the internal image buffers and mask vector will be cleared, but
// the paths are saved in case the sprite needs to be loaded again.
void Upload(Sprite *sprite);
void Upload(Sprite *sprite, bool enableUpload);


private:
Expand Down
24 changes: 14 additions & 10 deletions source/Sprite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,9 @@ const string &Sprite::Name() const



// Upload the given frames. The given buffer will be cleared afterwards.
// Add the given frames, optionally uploading them. The given buffer will be cleared afterwards.
void Sprite::AddFrames(ImageBuffer &buffer, bool is2x)
{
// Do nothing if the buffer is empty.
if(!buffer.Pixels())
return;

// If this is the 1x image, its dimensions determine the sprite's size.
if(!is2x)
{
Expand All @@ -87,7 +83,9 @@ void Sprite::AddFrames(ImageBuffer &buffer, bool is2x)
frames = buffer.Frames();
}

AddBuffer(buffer, &texture[is2x]);
// Only non-empty buffers need to be added to the sprite.
if(buffer.Pixels())
AddBuffer(buffer, &texture[is2x]);
}


Expand All @@ -107,11 +105,17 @@ void Sprite::AddSwizzleMaskFrames(ImageBuffer &buffer, bool is2x)
// Free up all textures loaded for this sprite.
void Sprite::Unload()
{
glDeleteTextures(2, texture);
texture[0] = texture[1] = 0;
if(texture[0] || texture[1])
{
glDeleteTextures(2, texture);
texture[0] = texture[1] = 0;
}

glDeleteTextures(2, swizzleMask);
swizzleMask[0] = swizzleMask[1] = 0;
if(swizzleMask[0] || swizzleMask[1])
{
glDeleteTextures(2, swizzleMask);
swizzleMask[0] = swizzleMask[1] = 0;
}

width = 0.f;
height = 0.f;
Expand Down
2 changes: 1 addition & 1 deletion source/Sprite.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class Sprite {

const std::string &Name() const;

// Upload the given frames. The given buffer will be cleared afterwards.
// Add the given frames, optionally uploading them. The given buffer will be cleared afterwards.
void AddFrames(ImageBuffer &buffer, bool is2x);
void AddSwizzleMaskFrames(ImageBuffer &buffer, bool is2x);
// Free up all textures loaded for this sprite.
Expand Down
10 changes: 9 additions & 1 deletion source/SpriteQueue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ void SpriteQueue::Finish()



// Don't upload the images to the GPU using OpenGL. Used for the integration tests.
void SpriteQueue::SetPreventUpload()
{
uploadSprites = false;
}



// Thread entry point.
void SpriteQueue::operator()()
{
Expand Down Expand Up @@ -198,7 +206,7 @@ void SpriteQueue::DoLoad(unique_lock<mutex> &lock)
lock.unlock();

readCondition.notify_one();
imageSet->Upload(SpriteSet::Modify(imageSet->Name()));
imageSet->Upload(SpriteSet::Modify(imageSet->Name()), uploadSprites);

lock.lock();
++completed;
Expand Down
6 changes: 6 additions & 0 deletions source/SpriteQueue.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ class SpriteQueue {
// Finish loading.
void Finish();

// Don't upload the images to the GPU using OpenGL. Used for the integration tests.
void SetPreventUpload();

// Thread entry point.
void operator()();

Expand All @@ -82,6 +85,9 @@ class SpriteQueue {

// Worker threads for loading sprites from disk.
std::vector<std::thread> threads;

// Flag to control whether to upload the sprites to OpenGL.
bool uploadSprites = true;
};

#endif
61 changes: 28 additions & 33 deletions source/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,20 +124,22 @@ int main(int argc, char *argv[])
printData = PrintData::IsPrintDataArgument(argv);
Files::Init(argv);

// Whether we are running an integration test.
const bool isTesting = !testToRunName.empty();
try {
// Load plugin preferences before game data if any.
Plugins::LoadSettings();

// Begin loading the game data.
bool isConsoleOnly = loadOnly || printTests || printData;
future<void> dataLoading = GameData::BeginLoad(isConsoleOnly, debugMode);
future<void> dataLoading = GameData::BeginLoad(isConsoleOnly, debugMode, isTesting && !debugMode);

// If we are not using the UI, or performing some automated task, we should load
// all data now. (Sprites and sounds can safely be deferred.)
if(isConsoleOnly || !testToRunName.empty())
if(isConsoleOnly || isTesting)
dataLoading.wait();

if(!testToRunName.empty() && !GameData::Tests().Has(testToRunName))
if(isTesting && !GameData::Tests().Has(testToRunName))
{
Logger::LogError("Test \"" + testToRunName + "\" not found.");
return 1;
Expand Down Expand Up @@ -183,20 +185,23 @@ int main(int argc, char *argv[])
if(node.Token(0) == "conditions")
GameData::GlobalConditions().Load(node);

if(!GameWindow::Init())
if(!GameWindow::Init(isTesting && !debugMode))
return 1;

GameData::LoadShaders();
GameData::LoadSettings();

// Show something other than a blank window.
GameWindow::Step();
if(!isTesting || debugMode)
{
GameData::LoadShaders();

// Show something other than a blank window.
GameWindow::Step();
}

Audio::Init(GameData::Sources());

if(!testToRunName.empty() && !noTestMute)
{
if(isTesting && !noTestMute)
Audio::SetVolume(0);
}

// This is the main loop where all the action begins.
GameLoop(player, conversation, testToRunName, debugMode);
Expand All @@ -208,8 +213,7 @@ int main(int argc, char *argv[])
catch(const runtime_error &error)
{
Audio::Quit();
bool doPopUp = testToRunName.empty();
GameWindow::ExitWithError(error.what(), doPopUp);
GameWindow::ExitWithError(error.what(), !isTesting);
return 1;
}

Expand Down Expand Up @@ -265,6 +269,8 @@ void GameLoop(PlayerInfo &player, const Conversation &conversation, const string
if(!testToRunName.empty())
testContext = TestContext(GameData::Tests().Get(testToRunName));

const bool isHeadless = (testContext.CurrentTest() && !debugMode);

// IsDone becomes true when the game is quit.
while(!menuPanels.IsDone())
{
Expand Down Expand Up @@ -366,16 +372,6 @@ void GameLoop(PlayerInfo &player, const Conversation &conversation, const string
// Reset the visual delay.
testDebugUIDelay = UI_DELAY;
}
// Skip drawing 29 out of every 30 in-flight frames during testing to speedup testing (unless debug mode is set).
// We don't skip UI-frames to ensure we test the UI code more.
if(inFlight && !debugMode)
{
skipFrame = (skipFrame + 1) % 30;
if(skipFrame)
continue;
}
else
skipFrame = 0;
}
// Caps lock slows the frame rate in debug mode.
// Slowing eases in and out over a couple of frames.
Expand Down Expand Up @@ -403,20 +399,19 @@ void GameLoop(PlayerInfo &player, const Conversation &conversation, const string
}
}

Audio::Step();

// Events in this frame may have cleared out the menu, in which case
// we should draw the game panels instead:
(menuPanels.IsEmpty() ? gamePanels : menuPanels).DrawAll();
if(isFastForward)
SpriteShader::Draw(SpriteSet::Get("ui/fast forward"), Screen::TopLeft() + Point(10., 10.));
if(!isHeadless)
{
Audio::Step();

GameWindow::Step();
// Events in this frame may have cleared out the menu, in which case
// we should draw the game panels instead:
(menuPanels.IsEmpty() ? gamePanels : menuPanels).DrawAll();
if(isFastForward)
SpriteShader::Draw(SpriteSet::Get("ui/fast forward"), Screen::TopLeft() + Point(10., 10.));

// When we perform automated testing, then we run the game by default as quickly as possible.
// Except when debug-mode is set.
if(!testContext.CurrentTest() || debugMode)
GameWindow::Step();
timer.Wait();
}

// If the player ended this frame in-game, count the elapsed time as played time.
if(menuPanels.IsEmpty())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ test "Wait 10 steps"


test "Start Game"
status active
status broken
description "Test if one can start the game correctly: choose a name and buy a ship."
sequence
watchdog 1000
Expand Down

0 comments on commit 4be1d8c

Please sign in to comment.