Template stripped to the overlay platform (examples, demo UI and Frame test package removed), renamed to FrameAssistant, and working rules for the project recorded in CLAUDE.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
117 lines
4.5 KiB
C++
117 lines
4.5 KiB
C++
// 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.
|
|
// Exit codes: 0 pass, 1 fail, 77 skipped (no GPU context).
|
|
|
|
#include <QDir>
|
|
#include <QImage>
|
|
#include <QTemporaryDir>
|
|
#include <cstdio>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#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) {
|
|
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()
|
|
int loadingCalls = 0;
|
|
int startedCalls = 0;
|
|
bool ok = true;
|
|
|
|
protected:
|
|
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 = <app key>.<id>");
|
|
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<Surface*> 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");
|
|
std::vector<std::string> args = {argv[0], "--snapshot", 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<char*> 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");
|
|
const int result = app.exec();
|
|
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");
|
|
|
|
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;
|
|
}
|