Platform: prepare() hook and app-specific command-line options

prepare() runs once mode and app key are known, before engine, surfaces
and SteamVR; returning false ends exec() with exit code 1 (D21). The base
constructor takes the app's own QCommandLineOptions, parses them with its
own and exposes them via isOptionSet()/optionValue() (D38).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 22:06:09 +02:00
co-authored by Claude Opus 5.5
parent 6bdf0c2690
commit 4f47874a4b
4 changed files with 85 additions and 9 deletions
+2
View File
@@ -31,5 +31,7 @@ add_test(NAME surfaces_opengl COMMAND tst_surfaces --graphics-api opengl)
add_test(NAME surfaces_vulkan COMMAND tst_surfaces --graphics-api vulkan)
add_test(NAME surfaces_cli_key COMMAND tst_surfaces --graphics-api opengl)
set_tests_properties(surfaces_cli_key PROPERTIES ENVIRONMENT "VRDASH_TEST_CLI_KEY=1")
add_test(NAME surfaces_prepare_fail COMMAND tst_surfaces --graphics-api opengl)
set_tests_properties(surfaces_prepare_fail PROPERTIES ENVIRONMENT "VRDASH_TEST_PREPARE_FAIL=1")
set_tests_properties(surfaces_opengl surfaces_vulkan surfaces_cli_key
PROPERTIES SKIP_RETURN_CODE 77)
+48 -6
View File
@@ -1,8 +1,12 @@
// An app with two surfaces, run in snapshot mode: checks surface set-up
// (overlay keys, names, sizes, shared engine, backends), the lifecycle hooks
// and that every surface renders its own scene. No SteamVR involved.
// (overlay keys, names, sizes, shared engine, backends), the lifecycle hooks,
// the app's own command-line options and that every surface renders its own
// scene. No SteamVR involved.
// VRDASH_TEST_PREPARE_FAIL=1: prepare() fails, so exec() must return 1
// without calling the other hooks.
// Exit codes: 0 pass, 1 fail, 77 skipped (no GPU context).
#include <QCommandLineOption>
#include <QDir>
#include <QImage>
#include <QTemporaryDir>
@@ -21,7 +25,14 @@ bool check(bool condition, const char* what) {
class TwoSurfaceApp : public DashboardApplication {
public:
TwoSurfaceApp(int& argc, char** argv) : DashboardApplication(argc, argv) {
TwoSurfaceApp(int& argc, char** argv)
: DashboardApplication(
argc, argv,
{QCommandLineOption(QStringLiteral("test-value"),
QStringLiteral("An app option."),
QStringLiteral("value")),
QCommandLineOption(QStringLiteral("test-flag"),
QStringLiteral("An app flag, never set."))}) {
setQmlModule(QStringLiteral("SurfacesTest"));
setDisplayName(QStringLiteral("Test App"));
setAppKey(QStringLiteral("config.key"));
@@ -35,11 +46,28 @@ class TwoSurfaceApp : public DashboardApplication {
}
QString expectedKey; // --app-key overrides setAppKey()
bool failPrepare = false;
int prepareCalls = 0;
int loadingCalls = 0;
int startedCalls = 0;
bool ok = true;
protected:
bool prepare(QString* error) override {
++prepareCalls;
ok &= check(loadingCalls == 0 && startedCalls == 0, "prepare() first");
ok &= check(engine() == nullptr && surfaces().isEmpty(),
"prepare() before engine and surfaces");
ok &= check(mode() == DashboardMode::Snapshot, "mode in prepare()");
ok &= check(appKey() == expectedKey, "app key in prepare()");
ok &= check(
isOptionSet(QStringLiteral("test-value")) &&
optionValue(QStringLiteral("test-value")) == QLatin1String("42"),
"app option value");
ok &= check(!isOptionSet(QStringLiteral("test-flag")), "app option unset");
if (failPrepare) *error = QStringLiteral("prepare() failed on purpose");
return !failPrepare;
}
void loading() override {
++loadingCalls;
Surface* red = surface(QStringLiteral("red"));
@@ -80,7 +108,10 @@ int main(int argc, char* argv[]) {
QDir(dir.path()).filePath(QStringLiteral("shot.png")).toStdString();
// VRDASH_TEST_CLI_KEY=1: also pass --app-key (must win over setAppKey()).
const bool cliKey = qEnvironmentVariableIntValue("VRDASH_TEST_CLI_KEY");
std::vector<std::string> args = {argv[0], "--snapshot", snapshot};
const bool failPrepare =
qEnvironmentVariableIntValue("VRDASH_TEST_PREPARE_FAIL");
std::vector<std::string> args = {argv[0], "--snapshot", snapshot,
"--test-value", "42"};
if (cliKey) {
args.emplace_back("--app-key");
args.emplace_back("cli.key");
@@ -93,15 +124,26 @@ int main(int argc, char* argv[]) {
TwoSurfaceApp app(cargc, cargs.data());
app.expectedKey =
cliKey ? QStringLiteral("cli.key") : QStringLiteral("config.key");
app.failPrepare = failPrepare;
const int result = app.exec();
if (failPrepare) {
bool ok = app.ok;
ok &= check(result == 1, "exec() returns 1 when prepare() fails");
ok &= check(
app.prepareCalls == 1 && app.loadingCalls == 0 && app.startedCalls == 0,
"no hooks after a failed prepare()");
fprintf(stderr, ok ? "PASS\n" : "FAILED\n");
return ok ? 0 : 1;
}
if (app.loadingCalls == 0) {
fprintf(stderr, "SKIP: no render backend (no GPU context?)\n");
return 77;
}
bool ok = app.ok;
ok &= check(result == 0, "exec() succeeded");
ok &= check(app.loadingCalls == 1 && app.startedCalls == 1,
"each hook called once");
ok &= check(
app.prepareCalls == 1 && app.loadingCalls == 1 && app.startedCalls == 1,
"each hook called once");
const QImage red(QDir(dir.path()).filePath(QStringLiteral("shot-red.png")));
const QImage blue(QDir(dir.path()).filePath(QStringLiteral("shot-blue.png")));