feat(tests): Add bgfx_gui_service_tests and integrate with CMake and dev commands

This commit is contained in:
2026-01-07 13:01:26 +00:00
parent 58e54bc2df
commit 23e22c63c0
3 changed files with 328 additions and 1 deletions

View File

@@ -327,6 +327,26 @@ target_link_libraries(script_engine_tests PRIVATE
Vorbis::vorbis
)
add_test(NAME script_engine_tests COMMAND script_engine_tests)
add_executable(bgfx_gui_service_tests
tests/test_bgfx_gui_service.cpp
src/services/impl/bgfx_gui_service.cpp
)
target_include_directories(bgfx_gui_service_tests PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(bgfx_gui_service_tests PRIVATE
${SDL_TARGET}
${SDL3CPP_RENDER_STACK_LIBS}
${SDL3CPP_FREETYPE_LIBS}
lunasvg::lunasvg
)
if(TARGET shaderc::shaderc)
target_link_libraries(bgfx_gui_service_tests PRIVATE shaderc::shaderc)
elseif(TARGET shaderc::shaderc_combined)
target_link_libraries(bgfx_gui_service_tests PRIVATE shaderc::shaderc_combined)
else()
message(FATAL_ERROR "shaderc CMake target not found")
endif()
add_test(NAME bgfx_gui_service_tests COMMAND bgfx_gui_service_tests)
endif()
add_executable(gxm_backend_tests

View File

@@ -315,6 +315,35 @@ def build(args: argparse.Namespace) -> None:
run_argvs([cmd], args.dry_run, env_overrides=vita_env)
def tests(args: argparse.Namespace) -> None:
"""Build (optional) and run ctest for a given build directory."""
build_dir = _as_build_dir(args.build_dir, DEFAULT_BUILD_DIR)
vita_env = _resolve_vita_env_for_build(build_dir)
argvs: list[list[str]] = []
if args.build_first:
build_cmd: list[str] = ["cmake", "--build", build_dir]
if args.config:
build_cmd.extend(["--config", args.config])
if args.target:
build_cmd.extend(["--target", args.target])
build_tool_args = _strip_leading_double_dash(args.build_tool_args)
if build_tool_args:
build_cmd.append("--")
build_cmd.extend(build_tool_args)
argvs.append(build_cmd)
ctest_cmd: list[str] = ["ctest", "--output-on-failure", "--test-dir", build_dir]
if args.config:
ctest_cmd.extend(["-C", args.config])
ctest_args = _strip_leading_double_dash(args.ctest_args)
if ctest_args:
ctest_cmd.extend(ctest_args)
argvs.append(ctest_cmd)
run_argvs(argvs, args.dry_run, env_overrides=vita_env)
def _cmd_one_liner_vcvars_then(bat: str, arch: str, then_parts: Sequence[str]) -> list[str]:
"""
Construct a command to call a Visual Studio environment setup batch file and
@@ -552,7 +581,13 @@ def gui(args: argparse.Namespace) -> None:
layout.addRow("Build Type:", self.build_type_combo)
self.target_combo = QComboBox()
self.target_combo.addItems(["sdl3_app", "all"])
self.target_combo.addItems([
"sdl3_app",
"all",
"script_engine_tests",
"gxm_backend_tests",
"bgfx_gui_service_tests",
])
layout.addRow("Target:", self.target_combo)
buttons = QDialogButtonBox(
@@ -1139,6 +1174,10 @@ def gui(args: argparse.Namespace) -> None:
build_action.triggered.connect(self.run_build)
dev_menu.addAction(build_action)
tests_action = QAction("Run Tests", self)
tests_action.triggered.connect(self.run_tests)
dev_menu.addAction(tests_action)
sync_action = QAction("Sync Assets", self)
sync_action.triggered.connect(self.sync_assets)
dev_menu.addAction(sync_action)
@@ -1702,6 +1741,25 @@ return {{
vita_env = _resolve_vita_env_for_build(build_dir)
self.run_command(cmd, env_overrides=vita_env)
def run_tests(self):
"""Build (optional) and run tests"""
if self.preset != "default":
build_dir = f"build-{self.preset.split('-')[0]}" # e.g., build-vita
else:
build_dir = GENERATOR_DEFAULT_DIR.get(self.generator, DEFAULT_BUILD_DIR)
cmd = [
sys.executable, __file__, "tests",
"--build-dir", build_dir,
"--config", self.build_type,
"--target", "all"
]
vita_env = None
if self.preset in VITA_PRESETS:
vita_env = _vita_env_overrides(f"preset {self.preset}")
else:
vita_env = _resolve_vita_env_for_build(build_dir)
self.run_command(cmd, env_overrides=vita_env)
def sync_assets(self):
"""Sync assets into the active build directory"""
if self.preset != "default":
@@ -1785,6 +1843,40 @@ def main() -> int:
),
)
bld.set_defaults(func=build)
tst = subparsers.add_parser("tests", help="build (optional) and run ctest")
tst.add_argument(
"--build-dir", default=DEFAULT_BUILD_DIR, help="which directory to test"
)
tst.add_argument(
"--config", default="Release", help="configuration for multi-config generators"
)
tst.add_argument(
"--target",
default="all",
help="target to build before tests (use --no-build to skip)",
)
tst.add_argument(
"--no-build",
action="store_true",
help="skip build step and only run tests",
)
tst.add_argument(
"--build-tool-args",
nargs=argparse.REMAINDER,
help=(
"extra args forwarded to the underlying build tool after `--` "
"(prefix with '--' before the tool args if needed)"
),
)
tst.add_argument(
"--ctest-args",
nargs=argparse.REMAINDER,
help=(
"extra arguments forwarded to ctest "
"(prefix with '--' before ctest flags if needed)"
),
)
tst.set_defaults(func=tests, build_first=True)
msvc = subparsers.add_parser(
"msvc-quick", help="run a VS env setup + follow-on command (README one-liner style)"
)
@@ -1849,6 +1941,8 @@ def main() -> int:
)
guip.set_defaults(func=gui)
args = parser.parse_args()
if hasattr(args, "no_build") and args.no_build:
args.build_first = False
try:
args.func(args)
except subprocess.CalledProcessError as exc:

View File

@@ -0,0 +1,213 @@
#define private public
#include "services/impl/bgfx_gui_service.hpp"
#undef private
#include "services/interfaces/i_config_service.hpp"
#include "services/interfaces/i_logger.hpp"
#include <bgfx/bgfx.h>
#include <filesystem>
#include <iostream>
#include <memory>
#include <string>
#include <utility>
#include <vector>
namespace {
class TestLogger : public sdl3cpp::services::ILogger {
public:
void SetLevel(sdl3cpp::services::LogLevel level) override {
level_ = level;
}
sdl3cpp::services::LogLevel GetLevel() const override {
return level_;
}
void SetOutputFile(const std::string& filename) override {
(void)filename;
}
void SetMaxLinesPerFile(size_t maxLines) override {
(void)maxLines;
}
void EnableConsoleOutput(bool enable) override {
consoleEnabled_ = enable;
}
void Log(sdl3cpp::services::LogLevel level, const std::string& message) override {
if (level < level_) {
return;
}
entries_.emplace_back(level, message);
if (consoleEnabled_) {
std::cout << message << '\n';
}
}
void Trace(const std::string& message) override {
Log(sdl3cpp::services::LogLevel::TRACE, message);
}
void Trace(const std::string& className,
const std::string& methodName,
const std::string& args = "",
const std::string& message = "") override {
std::string formattedMessage = className + "::" + methodName;
if (!args.empty()) {
formattedMessage += "(" + args + ")";
}
if (!message.empty()) {
formattedMessage += ": " + message;
}
Log(sdl3cpp::services::LogLevel::TRACE, formattedMessage);
}
void Debug(const std::string& message) override {
Log(sdl3cpp::services::LogLevel::DEBUG, message);
}
void Info(const std::string& message) override {
Log(sdl3cpp::services::LogLevel::INFO, message);
}
void Warn(const std::string& message) override {
Log(sdl3cpp::services::LogLevel::WARN, message);
}
void Error(const std::string& message) override {
Log(sdl3cpp::services::LogLevel::ERROR, message);
}
void TraceFunction(const std::string& funcName) override {
Trace("Entering " + funcName);
}
void TraceVariable(const std::string& name, const std::string& value) override {
Trace(name + " = " + value);
}
void TraceVariable(const std::string& name, int value) override {
TraceVariable(name, std::to_string(value));
}
void TraceVariable(const std::string& name, size_t value) override {
TraceVariable(name, std::to_string(value));
}
void TraceVariable(const std::string& name, bool value) override {
TraceVariable(name, std::string(value ? "true" : "false"));
}
void TraceVariable(const std::string& name, float value) override {
TraceVariable(name, std::to_string(value));
}
void TraceVariable(const std::string& name, double value) override {
TraceVariable(name, std::to_string(value));
}
bool HasErrorSubstring(const std::string& fragment) const {
for (const auto& entry : entries_) {
if (entry.first == sdl3cpp::services::LogLevel::ERROR &&
entry.second.find(fragment) != std::string::npos) {
return true;
}
}
return false;
}
private:
sdl3cpp::services::LogLevel level_ = sdl3cpp::services::LogLevel::TRACE;
bool consoleEnabled_ = false;
std::vector<std::pair<sdl3cpp::services::LogLevel, std::string>> entries_;
};
class StubConfigService : public sdl3cpp::services::IConfigService {
public:
uint32_t GetWindowWidth() const override { return 1; }
uint32_t GetWindowHeight() const override { return 1; }
std::filesystem::path GetScriptPath() const override { return {}; }
bool IsLuaDebugEnabled() const override { return false; }
std::string GetWindowTitle() const override { return ""; }
const sdl3cpp::services::InputBindings& GetInputBindings() const override { return inputBindings_; }
const sdl3cpp::services::MouseGrabConfig& GetMouseGrabConfig() const override { return mouseGrabConfig_; }
const sdl3cpp::services::BgfxConfig& GetBgfxConfig() const override { return bgfxConfig_; }
const sdl3cpp::services::MaterialXConfig& GetMaterialXConfig() const override { return materialXConfig_; }
const std::vector<sdl3cpp::services::MaterialXMaterialConfig>& GetMaterialXMaterialConfigs() const override {
return materialXMaterials_;
}
const sdl3cpp::services::GuiFontConfig& GetGuiFontConfig() const override { return guiFontConfig_; }
const std::string& GetConfigJson() const override { return configJson_; }
void DisableFreeType() {
guiFontConfig_.useFreeType = false;
}
private:
sdl3cpp::services::InputBindings inputBindings_{};
sdl3cpp::services::MouseGrabConfig mouseGrabConfig_{};
sdl3cpp::services::BgfxConfig bgfxConfig_{};
sdl3cpp::services::MaterialXConfig materialXConfig_{};
std::vector<sdl3cpp::services::MaterialXMaterialConfig> materialXMaterials_{};
sdl3cpp::services::GuiFontConfig guiFontConfig_{};
std::string configJson_{};
};
void Assert(bool condition, const std::string& message, int& failures) {
if (!condition) {
std::cerr << "test failure: " << message << '\n';
++failures;
}
}
} // namespace
int main() {
int failures = 0;
bgfx::Init init{};
init.type = bgfx::RendererType::Noop;
init.resolution.width = 1;
init.resolution.height = 1;
init.resolution.reset = BGFX_RESET_NONE;
if (!bgfx::init(init)) {
std::cerr << "test failure: bgfx init failed (Noop renderer)" << '\n';
return 1;
}
try {
auto logger = std::make_shared<TestLogger>();
auto configService = std::make_shared<StubConfigService>();
configService->DisableFreeType();
sdl3cpp::services::impl::BgfxGuiService service(configService, logger);
service.InitializeResources();
Assert(bgfx::isValid(service.program_), "GUI shader program should link", failures);
Assert(bgfx::isValid(service.whiteTexture_), "white texture should be created", failures);
if (!bgfx::isValid(service.program_) &&
!logger->HasErrorSubstring("bgfx::createProgram failed to link shaders")) {
Assert(false, "missing bgfx::createProgram link failure log", failures);
}
service.Shutdown();
} catch (const std::exception& ex) {
std::cerr << "exception during bgfx gui service tests: " << ex.what() << '\n';
bgfx::shutdown();
return 1;
}
bgfx::shutdown();
if (failures == 0) {
std::cout << "bgfx_gui_service_tests: PASSED" << '\n';
} else {
std::cerr << "bgfx_gui_service_tests: FAILED (" << failures << " errors)" << '\n';
}
return failures;
}