// An app with two surfaces, run in snapshot mode: checks surface set-up // (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. // VRDASH_TEST_DESKTOP_NOVR=1: --desktop-novr instead of --snapshot; prepare() // checks the mode and then fails (no window, no event loop). // Exit codes: 0 pass, 1 fail, 77 skipped (no GPU context). #include #include #include #include #include #include #include #include "app/DashboardApplication.h" namespace { bool check(bool condition, const char* what) { if (!condition) fprintf(stderr, "FAIL: %s\n", what); return condition; } class TwoSurfaceApp : public DashboardApplication { public: 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")); addSurface({.id = QStringLiteral("red"), .qmlRootType = QStringLiteral("Red"), .textureSize = QSize(64, 32)}); addSurface({.id = QStringLiteral("blue"), .qmlRootType = QStringLiteral("Blue"), .displayName = QStringLiteral("Blue tab"), .textureSize = QSize(32, 32)}); } QString expectedKey; // --app-key overrides setAppKey() DashboardMode expectedMode = DashboardMode::Snapshot; 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() == expectedMode, "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")); Surface* blue = surface(QStringLiteral("blue")); ok &= check(surfaces().size() == 2 && red && blue, "two surfaces by id"); ok &= check(surface() == red, "primary is the first"); ok &= check(surface(QStringLiteral("none")) == nullptr, "unknown id"); ok &= check(engine() != nullptr, "shared engine"); ok &= check(mode() == DashboardMode::Snapshot, "mode"); ok &= check(red && blue && red->backend() && blue->backend() && red->backend() != blue->backend(), "a backend per surface"); ok &= check(appKey() == expectedKey, "app key (setAppKey / --app-key)"); ok &= check(red && red->overlayKey() == expectedKey + QLatin1String(".red"), "overlay key = ."); ok &= check(red && red->displayName() == QLatin1String("Test App"), "tab name defaults to the app name"); ok &= check(blue && blue->displayName() == QLatin1String("Blue tab"), "tab name from config"); ok &= check(blue && blue->textureSize() == QSize(32, 32), "texture size from config"); ok &= check(vrRuntime() == nullptr, "no VR outside overlay mode"); } void started() override { ++startedCalls; const QList all = surfaces(); ok &= check(all.size() == 2 && all[0]->window() && all[1]->window() && all[0]->window() != all[1]->window(), "a window per surface"); } }; } // namespace int main(int argc, char* argv[]) { QTemporaryDir dir; const std::string snapshot = 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"); const bool desktopNoVr = qEnvironmentVariableIntValue("VRDASH_TEST_DESKTOP_NOVR"); const bool failPrepare = qEnvironmentVariableIntValue("VRDASH_TEST_PREPARE_FAIL") || desktopNoVr; std::vector args = {argv[0], "--test-value", "42"}; if (desktopNoVr) { args.emplace_back("--desktop-novr"); } else { args.emplace_back("--snapshot"); args.emplace_back(snapshot); } if (cliKey) { args.emplace_back("--app-key"); args.emplace_back("cli.key"); } for (int i = 1; i < argc; ++i) args.emplace_back(argv[i]); std::vector cargs; for (std::string& arg : args) cargs.push_back(arg.data()); int cargc = int(cargs.size()); TwoSurfaceApp app(cargc, cargs.data()); app.expectedKey = cliKey ? QStringLiteral("cli.key") : QStringLiteral("config.key"); app.failPrepare = failPrepare; if (desktopNoVr) app.expectedMode = DashboardMode::DesktopNoVr; 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.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"))); ok &= check(red.size() == QSize(64, 32) && red.pixelColor(10, 10) == QColor(255, 0, 0), "red surface rendered"); ok &= check(blue.size() == QSize(32, 32) && blue.pixelColor(10, 10) == QColor(0, 0, 255), "blue surface rendered"); fprintf(stderr, ok ? "PASS\n" : "FAILED\n"); return ok ? 0 : 1; }