More changes
This commit is contained in:
@@ -49,6 +49,13 @@ add_video_shader_test(RenderCadenceCompositorRuntimeLayerModelTests
|
||||
"${TEST_DIR}/RenderCadenceCompositorRuntimeLayerModelTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(RuntimeStatePersistenceTests
|
||||
"${SRC_DIR}/logging/Logger.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeJson.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeStatePersistence.cpp"
|
||||
"${TEST_DIR}/RuntimeStatePersistenceTests.cpp"
|
||||
)
|
||||
|
||||
add_video_shader_test(FontAtlasBuilderTests
|
||||
"${SRC_DIR}/runtime/FontAtlasBuilder.cpp"
|
||||
"${SRC_DIR}/runtime/RuntimeJson.cpp"
|
||||
|
||||
@@ -252,6 +252,27 @@ void TestInitializeFromRuntimeStateRestoresLayerStack()
|
||||
std::filesystem::remove_all(root);
|
||||
}
|
||||
|
||||
void TestInvalidRuntimeStateCanFallBackToConfiguredShader()
|
||||
{
|
||||
std::filesystem::path root;
|
||||
RenderCadenceCompositor::SupportedShaderCatalog catalog = MakeCatalog(root);
|
||||
|
||||
JsonValue invalidRuntimeState = JsonValue::MakeObject();
|
||||
invalidRuntimeState.set("layers", JsonValue("not-an-array"));
|
||||
|
||||
RenderCadenceCompositor::RuntimeLayerModel model;
|
||||
std::string error;
|
||||
Expect(!model.InitializeFromRuntimeState(catalog, invalidRuntimeState, error), "invalid runtime state is rejected");
|
||||
Expect(model.InitializeSingleLayer(catalog, "solid", error), "configured default shader can initialize after invalid runtime state");
|
||||
|
||||
RenderCadenceCompositor::RuntimeLayerModelSnapshot snapshot = model.Snapshot();
|
||||
Expect(snapshot.displayLayers.size() == 1, "fallback startup creates one default layer");
|
||||
Expect(snapshot.displayLayers[0].shaderId == "solid", "fallback layer uses the configured shader id");
|
||||
Expect(snapshot.displayLayers[0].parameterValues.at("gain").numberValues.front() == 0.5, "fallback layer uses shader defaults");
|
||||
|
||||
std::filesystem::remove_all(root);
|
||||
}
|
||||
|
||||
void TestLayerControlsUpdateDisplayAndRenderModels()
|
||||
{
|
||||
std::filesystem::path root;
|
||||
@@ -308,23 +329,42 @@ void TestReloadRefreshesChangedShaderMetadataAndPreservesValues()
|
||||
{
|
||||
std::filesystem::path root;
|
||||
RenderCadenceCompositor::SupportedShaderCatalog catalog = MakeCatalog(root);
|
||||
WriteFile(root / "passthrough" / "shader.slang", "float4 shadeVideo(float2 uv) { return float4(uv, 1.0, 1.0); }\n");
|
||||
WriteFile(root / "passthrough" / "shader.json", R"({
|
||||
"id": "passthrough",
|
||||
"name": "Passthrough",
|
||||
"description": "Passthrough test shader",
|
||||
"category": "Tests",
|
||||
"entryPoint": "shadeVideo",
|
||||
"parameters": [
|
||||
{ "id": "opacity", "label": "Opacity", "type": "float", "default": 1.0 }
|
||||
]
|
||||
})");
|
||||
catalog = LoadCatalog(root);
|
||||
|
||||
RenderCadenceCompositor::RuntimeLayerModel model;
|
||||
std::string error;
|
||||
std::string layerId;
|
||||
std::string unchangedLayerId;
|
||||
Expect(model.AddLayer(catalog, "solid", layerId, error), "reload test layer can be added");
|
||||
Expect(model.AddLayer(catalog, "passthrough", unchangedLayerId, error), "reload unchanged layer can be added");
|
||||
Expect(model.UpdateParameter(layerId, "gain", JsonValue(0.75), error), "reload test parameter can be customized");
|
||||
|
||||
WriteFile(root / "solid" / "shader.json", SolidShaderManifest(0.1, true));
|
||||
RenderCadenceCompositor::SupportedShaderCatalog reloadedCatalog = LoadCatalog(root);
|
||||
std::vector<std::pair<std::string, std::string>> buildsToStart;
|
||||
Expect(model.ReloadFromCatalog(reloadedCatalog, buildsToStart, error), "reload refreshes model from changed catalog");
|
||||
Expect(buildsToStart.size() == 1 && buildsToStart[0].first == layerId && buildsToStart[0].second == "solid", "changed shader queues a layer rebuild");
|
||||
Expect(buildsToStart.size() == 2
|
||||
&& buildsToStart[0].first == layerId
|
||||
&& buildsToStart[0].second == "solid"
|
||||
&& buildsToStart[1].first == unchangedLayerId
|
||||
&& buildsToStart[1].second == "passthrough", "reload queues every available layer rebuild in order");
|
||||
|
||||
RenderCadenceCompositor::RuntimeLayerModelSnapshot snapshot = model.Snapshot();
|
||||
Expect(snapshot.displayLayers[0].parameterDefinitions.size() == 3, "reload exposes new JSON parameter definitions to UI");
|
||||
Expect(snapshot.displayLayers[0].parameterValues.at("gain").numberValues.front() == 0.75, "reload preserves compatible parameter values");
|
||||
Expect(snapshot.displayLayers[0].parameterValues.at("mix").numberValues.front() == 0.25, "reload initializes newly added parameters");
|
||||
Expect(snapshot.displayLayers[1].parameterValues.at("opacity").numberValues.front() == 1.0, "reload keeps unchanged layer parameter values");
|
||||
|
||||
std::filesystem::remove_all(root);
|
||||
}
|
||||
@@ -337,6 +377,7 @@ int main()
|
||||
TestBuildFailureStaysDisplaySide();
|
||||
TestAddAndRemoveLayers();
|
||||
TestInitializeFromRuntimeStateRestoresLayerStack();
|
||||
TestInvalidRuntimeStateCanFallBackToConfiguredShader();
|
||||
TestLayerControlsUpdateDisplayAndRenderModels();
|
||||
TestReloadRefreshesChangedShaderMetadataAndPreservesValues();
|
||||
|
||||
|
||||
157
tests/RuntimeStatePersistenceTests.cpp
Normal file
157
tests/RuntimeStatePersistenceTests.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
#include "RuntimeStatePersistence.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace
|
||||
{
|
||||
int gFailures = 0;
|
||||
|
||||
using RenderCadenceCompositor::RuntimeLayerModelSnapshot;
|
||||
using RenderCadenceCompositor::RuntimeLayerReadModel;
|
||||
using RenderCadenceCompositor::SerializeRuntimeStateSnapshot;
|
||||
|
||||
void Expect(bool condition, const char* message)
|
||||
{
|
||||
if (condition)
|
||||
return;
|
||||
|
||||
std::cerr << "FAIL: " << message << "\n";
|
||||
++gFailures;
|
||||
}
|
||||
|
||||
std::filesystem::path MakeTestRoot()
|
||||
{
|
||||
const auto stamp = std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
std::filesystem::path root = std::filesystem::temp_directory_path() / ("render-cadence-runtime-state-persistence-tests-" + std::to_string(stamp));
|
||||
std::filesystem::create_directories(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
std::string ReadTextFile(const std::filesystem::path& path)
|
||||
{
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
std::ostringstream buffer;
|
||||
buffer << input.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
ShaderParameterDefinition FloatParameter()
|
||||
{
|
||||
ShaderParameterDefinition definition;
|
||||
definition.id = "gain";
|
||||
definition.type = ShaderParameterType::Float;
|
||||
return definition;
|
||||
}
|
||||
|
||||
ShaderParameterDefinition BooleanParameter()
|
||||
{
|
||||
ShaderParameterDefinition definition;
|
||||
definition.id = "enabled";
|
||||
definition.type = ShaderParameterType::Boolean;
|
||||
return definition;
|
||||
}
|
||||
|
||||
RuntimeLayerModelSnapshot MakeSnapshot(const std::string& layerId, double gain, bool enabled)
|
||||
{
|
||||
RuntimeLayerReadModel layer;
|
||||
layer.id = layerId;
|
||||
layer.shaderId = "solid";
|
||||
layer.shaderName = "Solid";
|
||||
layer.bypass = !enabled;
|
||||
layer.parameterDefinitions = { FloatParameter(), BooleanParameter() };
|
||||
|
||||
ShaderParameterValue gainValue;
|
||||
gainValue.numberValues = { gain };
|
||||
layer.parameterValues["gain"] = gainValue;
|
||||
|
||||
ShaderParameterValue enabledValue;
|
||||
enabledValue.booleanValue = enabled;
|
||||
layer.parameterValues["enabled"] = enabledValue;
|
||||
|
||||
RuntimeLayerModelSnapshot snapshot;
|
||||
snapshot.displayLayers.push_back(layer);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
void TestRuntimeStateSerialization()
|
||||
{
|
||||
const std::string serialized = SerializeRuntimeStateSnapshot(MakeSnapshot("layer-a", 0.75, false));
|
||||
JsonValue parsed;
|
||||
std::string error;
|
||||
Expect(ParseJson(serialized, parsed, error), "serialized runtime state parses");
|
||||
const JsonValue* layers = parsed.find("layers");
|
||||
Expect(layers && layers->isArray() && layers->asArray().size() == 1, "serialized runtime state contains one layer");
|
||||
const JsonValue& layer = layers->asArray().front();
|
||||
Expect(layer.find("id") && layer.find("id")->asString() == "layer-a", "serialized runtime state preserves layer id");
|
||||
Expect(layer.find("shaderId") && layer.find("shaderId")->asString() == "solid", "serialized runtime state preserves shader id");
|
||||
Expect(layer.find("bypass") && layer.find("bypass")->asBoolean() == true, "serialized runtime state preserves bypass flag");
|
||||
const JsonValue* values = layer.find("parameterValues");
|
||||
Expect(values && values->isObject(), "serialized runtime state contains parameter values");
|
||||
Expect(values->find("gain") && values->find("gain")->asNumber() == 0.75, "serialized runtime state preserves float values");
|
||||
Expect(values->find("enabled") && values->find("enabled")->asBoolean() == false, "serialized runtime state preserves boolean values");
|
||||
}
|
||||
|
||||
void TestRuntimeStateWriterFlushesLatestSnapshot()
|
||||
{
|
||||
const std::filesystem::path root = MakeTestRoot();
|
||||
const std::filesystem::path path = root / "runtime_state.json";
|
||||
|
||||
RenderCadenceCompositor::RuntimeStatePersistenceWriter writer;
|
||||
writer.Start(path, std::chrono::milliseconds(1000));
|
||||
writer.RequestSave(MakeSnapshot("layer-old", 0.25, true));
|
||||
writer.RequestSave(MakeSnapshot("layer-new", 0.9, false));
|
||||
writer.Stop(true);
|
||||
|
||||
JsonValue parsed;
|
||||
std::string error;
|
||||
Expect(ParseJson(ReadTextFile(path), parsed, error), "flushed runtime state parses");
|
||||
const JsonValue& layer = parsed.find("layers")->asArray().front();
|
||||
Expect(layer.find("id")->asString() == "layer-new", "writer flushes the latest pending snapshot");
|
||||
Expect(layer.find("parameterValues")->find("gain")->asNumber() == 0.9, "writer flush keeps latest parameter value");
|
||||
|
||||
std::filesystem::remove_all(root);
|
||||
}
|
||||
|
||||
void TestRuntimeStateWriterDebouncesAndWritesInBackground()
|
||||
{
|
||||
const std::filesystem::path root = MakeTestRoot();
|
||||
const std::filesystem::path path = root / "nested" / "runtime_state.json";
|
||||
|
||||
{
|
||||
RenderCadenceCompositor::RuntimeStatePersistenceWriter writer;
|
||||
writer.Start(path, std::chrono::milliseconds(20));
|
||||
writer.RequestSave(MakeSnapshot("layer-bg", 0.5, true));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(120));
|
||||
writer.Stop(true);
|
||||
}
|
||||
|
||||
JsonValue parsed;
|
||||
std::string error;
|
||||
Expect(ParseJson(ReadTextFile(path), parsed, error), "background runtime state write parses");
|
||||
Expect(parsed.find("layers")->asArray().front().find("id")->asString() == "layer-bg", "background writer creates the runtime state file");
|
||||
|
||||
std::filesystem::remove_all(root);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
TestRuntimeStateSerialization();
|
||||
TestRuntimeStateWriterFlushesLatestSnapshot();
|
||||
TestRuntimeStateWriterDebouncesAndWritesInBackground();
|
||||
|
||||
if (gFailures != 0)
|
||||
{
|
||||
std::cerr << gFailures << " RuntimeStatePersistence test failure(s).\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "RuntimeStatePersistence tests passed.\n";
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user