diagnostics: add STEP-099 render source readiness audit

This commit is contained in:
tei
2026-10-06 15:15:12 +09:00
parent e8b451fe70
commit 8a5214d92d
13 changed files with 915 additions and 9 deletions

View File

@@ -934,6 +934,115 @@ TEST(RenderSegmentDiagnostics, SnapshotCoversStyleExactlyAndSerializesContinuous
EXPECT_NE(json.find("\"cache\":null"), std::string::npos);
}
TEST(RenderSegmentDiagnostics, ReadinessSnapshotMapsSourcesToSegmentsWithoutSideEffects) {
RetainedUnderlayRuntimeState state;
RenderSegmentPlan configured;
configured.segments = {{RenderSegmentStrategy::Retained, std::nullopt},
{RenderSegmentStrategy::Continuous, "weather"},
{RenderSegmentStrategy::Retained, "land"}};
ASSERT_TRUE(state.setRenderSegmentPlan(configured));
const auto resolved = resolveRenderSegmentPlan(state.renderSegmentPlanConfig(), segmentLayerIds(), true);
std::vector<StyleLayerDiagnostics> layers;
for (std::size_t i = 0; i < segmentLayerIds().size(); ++i) {
layers.push_back({static_cast<int32_t>(i), segmentLayerIds()[i], "fill", true, std::nullopt, std::nullopt});
}
layers[1].sourceId = "chart";
layers[2].sourceId = "dynamic";
layers[3].sourceId = "dynamic";
layers[4].sourceId = "chart";
layers[5].layerType = "symbol";
layers[5].sourceId = "ais";
state.recordRenderSegmentResolution(resolved, layers);
state.recordSegmentBuild(0);
state.recordSegmentCacheState(0, RetainedRangeCacheState::Ready, 41);
RendererReadinessDiagnostics readiness;
readiness.styleLoaded = true;
readiness.imageManagerLoaded = true;
readiness.renderOrchestratorLoaded = false;
readiness.spriteLoaded = true;
readiness.hasFadingTiles = false;
readiness.contentRevision = 42;
readiness.sources = {
{.sourceId = "chart",
.sourceType = "vector",
.enabled = true,
.isLoaded = true,
.tileBacked = true,
.tileCount = 4,
.incompleteTileCount = 0,
.loadedTileCount = 4},
{.sourceId = "dynamic",
.sourceType = "raster",
.enabled = true,
.isLoaded = false,
.tileBacked = true,
.tileCount = 1,
.incompleteTileCount = 1,
.loadedTileCount = 0,
.lastSourceMutationSequence = 11,
.lastTileChangedSequence = 12,
.lastTileActionSequence = 13,
.lastTileAction = "StartParse",
.lastMutationReason = "tile-mutation"}};
readiness.lastMutation = {.sequence = 12,
.contentRevision = 42,
.eventType = "TileMutation",
.reason = "tile-mutation",
.sourceId = "dynamic"};
state.recordRendererReadiness(readiness);
const auto first = state.renderSegmentSnapshot();
ASSERT_EQ(first.sources.size(), 2u);
EXPECT_TRUE(first.styleLoaded);
EXPECT_TRUE(first.imageManagerLoaded);
EXPECT_FALSE(first.renderOrchestratorLoaded);
EXPECT_EQ(first.contentRevision, 42u);
EXPECT_EQ(first.sources[0].sourceId, "chart");
EXPECT_TRUE(first.sources[0].isLoaded);
EXPECT_EQ(first.sources[0].tileCount, std::optional<uint32_t>{4});
EXPECT_EQ(first.sources[1].sourceId, "dynamic");
EXPECT_FALSE(first.sources[1].isLoaded);
EXPECT_EQ(first.sources[1].incompleteTileCount, std::optional<uint32_t>{1});
ASSERT_EQ(first.segments.size(), 3u);
EXPECT_EQ(first.segments[0].dependentSourceIds, std::vector<std::string>({"chart"}));
EXPECT_EQ(first.segments[1].dependentSourceIds, std::vector<std::string>({"dynamic"}));
EXPECT_EQ(first.segments[2].dependentSourceIds, std::vector<std::string>({"ais", "chart"}));
EXPECT_TRUE(first.segments[0].unknownDependencies.empty());
EXPECT_EQ(first.segments[2].unknownDependencies,
std::vector<std::string>({"symbol/glyph/image/placement"}));
EXPECT_EQ(first.segments[0].dependentSourceIds.size(), 1u);
const auto backgroundLayer = std::find_if(first.segments[0].layers.begin(), first.segments[0].layers.end(),
[](const auto& layer) { return layer.layerId == "background"; });
ASSERT_NE(backgroundLayer, first.segments[0].layers.end());
EXPECT_FALSE(backgroundLayer->sourceId.has_value());
EXPECT_EQ(first.lastMutation.affectedSegmentIndices, std::vector<uint32_t>({1}));
EXPECT_EQ(first.lastMutation.dependencyScope, "source-local-diagnostic");
const auto topologyRevision = first.topologyRevision;
const auto cacheBuild = first.segments[0].cache->build;
state.recordRendererReadiness(readiness);
const auto second = state.renderSegmentSnapshot();
EXPECT_EQ(second.topologyRevision, topologyRevision);
ASSERT_TRUE(second.segments[0].cache);
EXPECT_EQ(second.segments[0].cache->build, cacheBuild);
EXPECT_EQ(second.segments[0].cache->contentRevision, 41u);
const auto third = state.renderSegmentSnapshot();
EXPECT_EQ(third.topologyRevision, second.topologyRevision);
EXPECT_EQ(third.contentRevision, second.contentRevision);
EXPECT_EQ(third.sources, second.sources);
EXPECT_EQ(third.lastMutation, second.lastMutation);
const auto json = renderSegmentDiagnosticsToJSON(second);
EXPECT_EQ(renderSegmentDiagnosticsToJSON(state.renderSegmentSnapshot()), json);
EXPECT_NE(json.find("\"renderOrchestratorLoaded\":false"), std::string::npos);
EXPECT_NE(json.find("\"incompleteTileCount\":1"), std::string::npos);
EXPECT_NE(json.find("\"dependentSourceIds\":[\"dynamic\"]"), std::string::npos);
EXPECT_NE(json.find("\"affectedSegmentIndices\":[1]"), std::string::npos);
}
TEST(RenderSegmentPlan, StrictJSONRejectsUnknownFieldsStrategyAndInvalidFirstBoundary) {
EXPECT_FALSE(parseRenderSegmentPlanJSON(
R"({"schemaVersion":1,"segments":[{"strategy":"RETAINED","unexpected":true}]})"));