From bbb5b68e91db6161ae71ddf5cc27bdb20c3fa2c6 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 26 Jul 2022 23:07:10 +0200 Subject: [PATCH] Refactor FlaxTests to run as Editor with all engine services initialized --- .github/workflows/tests.yml | 2 +- Source/Editor/Editor.cpp | 15 ++++++--- Source/Engine/Core/Config/GameSettings.cpp | 11 ++++-- Source/Engine/Engine/Engine.cpp | 21 ++++++++---- Source/Engine/Main/Windows/main.cpp | 9 +++++ Source/Engine/Tests/TestMain.cpp | 39 ++++++++++++++++++++-- Source/Engine/Tests/Tests.Build.cs | 10 ++++++ Source/FlaxTests.Build.cs | 13 +++----- 8 files changed, 94 insertions(+), 26 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 7a8a02626..5c6627512 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -35,4 +35,4 @@ jobs: - name: Test UseLargeWorlds run: | ./Development/Scripts/Linux/CallBuildTool.sh -build -log -arch=x64 -platform=Linux -configuration=Development -buildtargets=FlaxTestsTarget -UseLargeWorlds=true - Binaries/Tests/Linux/x64/Development/FlaxTests + Binaries/Editor/Development/Linux/FlaxTests diff --git a/Source/Editor/Editor.cpp b/Source/Editor/Editor.cpp index 16acfb054..0328c5b79 100644 --- a/Source/Editor/Editor.cpp +++ b/Source/Editor/Editor.cpp @@ -53,6 +53,7 @@ bool Editor::CheckProjectUpgrade() int32 lastMajor = FLAXENGINE_VERSION_MAJOR; int32 lastMinor = FLAXENGINE_VERSION_MINOR; int32 lastBuild = FLAXENGINE_VERSION_BUILD; + if (FileSystem::FileExists(versionFilePath)) { auto file = FileReadStream::Open(versionFilePath); if (file) @@ -77,10 +78,10 @@ bool Editor::CheckProjectUpgrade() Delete(file); } - else - { - LOG(Warning, "Missing version cache file"); - } + } + else + { + LOG(Warning, "Missing version cache file"); } // Check if project is in the old, deprecated layout @@ -365,6 +366,12 @@ int32 Editor::LoadProduct() Globals::ProductName = TEXT("Flax Editor"); Globals::CompanyName = TEXT("Flax"); +#if FLAX_TESTS + // Flax Tests use auto-generated temporary project + CommandLine::Options.Project = Globals::TemporaryFolder / TEXT("Project"); + CommandLine::Options.NewProject = true; +#endif + // Gather project directory from the command line String projectPath = CommandLine::Options.Project.TrimTrailing(); const int32 startIndex = projectPath.StartsWith('\"') || projectPath.StartsWith('\'') ? 1 : 0; diff --git a/Source/Engine/Core/Config/GameSettings.cpp b/Source/Engine/Core/Config/GameSettings.cpp index e96453b1d..66004afd9 100644 --- a/Source/Engine/Core/Config/GameSettings.cpp +++ b/Source/Engine/Core/Config/GameSettings.cpp @@ -21,11 +21,13 @@ #include "Engine/Engine/Globals.h" #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Streaming/StreamingSettings.h" +#if FLAX_TESTS +#include "Engine/Platform/FileSystem.h" +#endif class GameSettingsService : public EngineService { public: - GameSettingsService() : EngineService(TEXT("GameSettings"), -70) { @@ -49,7 +51,7 @@ IMPLEMENT_ENGINE_SETTINGS_GETTER(StreamingSettings, Streaming); #if !USE_EDITOR #if PLATFORM_WINDOWS IMPLEMENT_ENGINE_SETTINGS_GETTER(WindowsPlatformSettings, WindowsPlatform); -#elif PLATFORM_UWP +#elif PLATFORM_UWP IMPLEMENT_ENGINE_SETTINGS_GETTER(UWPPlatformSettings, UWPPlatform); #elif PLATFORM_LINUX IMPLEMENT_ENGINE_SETTINGS_GETTER(LinuxPlatformSettings, LinuxPlatform); @@ -83,6 +85,11 @@ GameSettings* GameSettings::Get() // It may be missing in editor during dev but must be ready in the build game. PROFILE_CPU(); const auto assetPath = Globals::ProjectContentFolder / TEXT("GameSettings.json"); +#if FLAX_TESTS + // Silence missing GameSettings during test run before Editor creates it (not important) + if (!FileSystem::FileExists(assetPath)) + return nullptr; +#endif GameSettingsAsset = Content::LoadAsync(assetPath); if (GameSettingsAsset == nullptr) { diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 76f249100..ae1fa0563 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -84,6 +84,14 @@ int32 Engine::Main(const Char* cmdLine) return -1; } +#if FLAX_TESTS + // Configure engine for test running environment + CommandLine::Options.Headless = true; + CommandLine::Options.Null = true; + CommandLine::Options.Mute = true; + CommandLine::Options.Std = true; +#endif + if (Platform::Init()) { Platform::Fatal(TEXT("Cannot init platform.")); @@ -102,6 +110,11 @@ int32 Engine::Main(const Char* cmdLine) StringUtils::PathRemoveRelativeParts(Globals::StartupFolder); FileSystem::NormalizePath(Globals::BinariesFolder); + FileSystem::GetSpecialFolderPath(SpecialFolder::Temporary, Globals::TemporaryFolder); + if (Globals::TemporaryFolder.IsEmpty()) + Platform::Fatal(TEXT("Failed to gather temporary folder directory.")); + Globals::TemporaryFolder /= Guid::New().ToString(Guid::FormatType::D); + // Load game info or project info { const int32 result = Application::LoadProduct(); @@ -512,12 +525,6 @@ void EngineImpl::InitLog() void EngineImpl::InitPaths() { - // Prepare temp folder path - FileSystem::GetSpecialFolderPath(SpecialFolder::Temporary, Globals::TemporaryFolder); - if (Globals::TemporaryFolder.IsEmpty()) - Platform::Fatal(TEXT("Failed to gather temporary folder directory.")); - Globals::TemporaryFolder /= Guid::New().ToString(Guid::FormatType::D); - // Cache other global paths FileSystem::GetSpecialFolderPath(SpecialFolder::LocalAppData, Globals::ProductLocalFolder); if (Globals::ProductLocalFolder.IsEmpty()) @@ -551,7 +558,7 @@ void EngineImpl::InitPaths() if (!Globals::StartupFolder.IsANSI()) Platform::Fatal(TEXT("Cannot start application in directory which name contains non-ANSI characters.")); -#if !PLATFORM_SWITCH +#if !PLATFORM_SWITCH && !FLAX_TESTS // Setup directories if (FileSystem::DirectoryExists(Globals::TemporaryFolder)) FileSystem::DeleteDirectory(Globals::TemporaryFolder); diff --git a/Source/Engine/Main/Windows/main.cpp b/Source/Engine/Main/Windows/main.cpp index c9ad1a696..1f0e9676e 100644 --- a/Source/Engine/Main/Windows/main.cpp +++ b/Source/Engine/Main/Windows/main.cpp @@ -27,8 +27,17 @@ __declspec(dllexport) int32 AmdPowerXpressRequestHighPerformance = 1; extern LONG CALLBACK SehExceptionHandler(EXCEPTION_POINTERS* ep); +#if FLAX_TESTS +int main(int argc, char* argv[]) +#else int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) +#endif { +#if FLAX_TESTS + HINSTANCE hInstance = GetModuleHandle(NULL); + LPTSTR lpCmdLine = GetCommandLineW(); +#endif + #ifdef USE_VS_MEM_LEAKS_CHECK // Memory leaks detect inside VS int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); diff --git a/Source/Engine/Tests/TestMain.cpp b/Source/Engine/Tests/TestMain.cpp index 3594c3a49..6dd3c8873 100644 --- a/Source/Engine/Tests/TestMain.cpp +++ b/Source/Engine/Tests/TestMain.cpp @@ -2,13 +2,46 @@ #if PLATFORM_WINDOWS || PLATFORM_LINUX || PLATFORM_MAC +#include "Engine/Core/Log.h" +#include "Engine/Engine/Engine.h" +#include "Engine/Engine/EngineService.h" +#include "Engine/Scripting/Scripting.h" +#include "Editor/Scripting/ScriptsBuilder.h" + #define CATCH_CONFIG_RUNNER #include -int main(int argc, char* argv[]) +class TestsRunnerService : public EngineService { - int result = Catch::Session().run(argc, argv); - return result; +public: + TestsRunnerService() + : EngineService(TEXT("TestsRunnerService"), 10000) + { + } + + void Update() override; +}; + +TestsRunnerService TestsRunnerServiceInstance; + +void TestsRunnerService::Update() +{ + // Wait for Editor to be ready for running tests (eg. scripting loaded) + if (!ScriptsBuilder::IsReady() || + !Scripting::IsEveryAssemblyLoaded() || + !Scripting::HasGameModulesLoaded()) + return; + + // Runs tests + Log::Logger::WriteFloor(); + LOG(Info, "Running Flax Tests..."); + const int result = Catch::Session().run(); + if (result == 0) + LOG(Info, "Result: {0}", result); + else + LOG(Error, "Result: {0}", result); + Log::Logger::WriteFloor(); + Engine::RequestExit(result); } #endif diff --git a/Source/Engine/Tests/Tests.Build.cs b/Source/Engine/Tests/Tests.Build.cs index 3d91dc7fd..229ad0c3d 100644 --- a/Source/Engine/Tests/Tests.Build.cs +++ b/Source/Engine/Tests/Tests.Build.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Flax.Build; +using Flax.Build.NativeCpp; /// /// Engine tests module. @@ -14,6 +15,15 @@ public class Tests : EngineModule Deploy = false; } + /// + public override void Setup(BuildOptions options) + { + base.Setup(options); + + options.PrivateDependencies.Add("Content"); + options.PrivateDependencies.Add("Level"); + } + /// public override void GetFilesToDeploy(List files) { diff --git a/Source/FlaxTests.Build.cs b/Source/FlaxTests.Build.cs index eb75c0ad6..1c02439f7 100644 --- a/Source/FlaxTests.Build.cs +++ b/Source/FlaxTests.Build.cs @@ -1,7 +1,6 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. using System; -using System.IO; using System.Linq; using Flax.Build; using Flax.Build.NativeCpp; @@ -9,7 +8,7 @@ using Flax.Build.NativeCpp; /// /// Target that builds standalone, native tests. /// -public class FlaxTestsTarget : EngineTarget +public class FlaxTestsTarget : FlaxEditor { /// public override void Init() @@ -35,8 +34,9 @@ public class FlaxTestsTarget : EngineTarget { TargetConfiguration.Development, }; + GlobalDefinitions.Add("FLAX_TESTS"); + Win32ResourceFile = null; - Modules.Remove("Main"); Modules.Add("Tests"); } @@ -45,13 +45,8 @@ public class FlaxTestsTarget : EngineTarget { base.SetupTargetEnvironment(options); + // Produce console program options.LinkEnv.LinkAsConsoleProgram = true; - - // Setup output folder for Test binaries - var platformName = options.Platform.Target.ToString(); - var architectureName = options.Architecture.ToString(); - var configurationName = options.Configuration.ToString(); - options.OutputFolder = Path.Combine(options.WorkingDirectory, "Binaries", "Tests", platformName, architectureName, configurationName); } ///