You've already forked ivi-homescreen
mirror of
https://github.com/toyota-connected/ivi-homescreen.git
synced 2026-06-21 07:19:30 -07:00
[compositor] golden-frame harness scaffolding
Establish the headless golden-frame test infrastructure for the compositor path. Single-layer parity gate is wired and runs; multi- layer (PlatformViewLayer) coverage drops in alongside it once a Dart bundle that emits one is staged. - compositor_headless_golden-test: SingleLayerFastPathParity boots the homescreen app via the OSMesa headless backend with BUILD_COMPOSITOR=ON, captures getViewRenderBuf(0), writes a .tga, and pixel-diffs against a stored golden. UNIT_TEST_SAVE_GOLDENS=ON generates the golden then fails with a clear regenerate-then-rerun message. Sanitizer support inherited from the typical-test scaffold. - docs/COMPOSITOR_GOLDENS.md: operator's guide. Prerequisites with Fedora and Ubuntu install lines (mesa-compat-libOSMesa, mesa-vulkan-drivers/libosmesa6), bundle requirements with a minimal Dart Stack+PlatformViewSurface example, run commands for parity and save-then-compare flows, sanitizer combos, weston-headless + lavapipe recipe for Vulkan goldens, and a "what's deferred" section listing HeadlessBackend compositor wiring (OSMesa is desktop GL not GLES — needs parallel implementation of GlCompositor/EglFboBackingStore), the absent native headless Vulkan backend, and bundle staging. - third_party/CMakeLists.txt: gtest and gtest_main get -Wno-error=sign-conversion / -Wno-error=conversion. GCC 15.2 promotes a sign-conversion warning inside googletest's gtest.cc (kSize template parameter) to an error under the project's -Werror toolchain settings, blocking any unit test build on this host. Project-side -Werror untouched — only the third-party gtest target gets the relaxation. All four pre-existing compositor unit tests (backing_store_pool, present_layer_sequencer, compositor_registry, mutation_stack) pass under ctest in this configuration. The new test driver runs to the bundle-load step against a fake bundle path; a real Flutter bundle is needed to complete the parity comparison. Default EGL and Vulkan+compositor non-test builds still link clean. Signed-off-by: Joel Winarske <joel.winarske@gmail.com>
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
# Compositor golden-frame tests
|
||||
|
||||
Pixel-diff regression gates for the compositor path. Two layers of coverage:
|
||||
|
||||
1. **Single-layer parity** — running the same Flutter app under `BUILD_COMPOSITOR=ON` should produce the same image as under `BUILD_COMPOSITOR=OFF`. Captured by `compositor_headless_golden-test`. This gate flips the moment a compositor change subtly perturbs a Flutter-only frame.
|
||||
2. **Multi-layer interleave** — Flutter content above and below a `PlatformViewSurface`, expected pixel patterns committed as goldens. Requires a Flutter bundle that emits the platform view; see [Bundle requirements](#bundle-requirements).
|
||||
|
||||
Both run against software rasterizers so they're CI-friendly.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### OSMesa (headless OpenGL)
|
||||
|
||||
Required for any EGL-side golden. The host's existing `BUILD_BACKEND_HEADLESS_EGL` already targets OSMesa.
|
||||
|
||||
Fedora:
|
||||
|
||||
```bash
|
||||
sudo dnf install mesa-compat-libOSMesa mesa-compat-libOSMesa-devel
|
||||
```
|
||||
|
||||
Ubuntu/Debian:
|
||||
|
||||
```bash
|
||||
sudo apt install libosmesa6 libosmesa6-dev
|
||||
```
|
||||
|
||||
### lavapipe (headless Vulkan)
|
||||
|
||||
Mesa's software Vulkan ICD. Used to drive `WaylandVulkanBackend` without GPU hardware. Already shipped with `mesa-vulkan-drivers` on most distros — verify with:
|
||||
|
||||
```bash
|
||||
ls /usr/share/vulkan/icd.d/lvp_icd.*.json
|
||||
VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json vulkaninfo --summary
|
||||
```
|
||||
|
||||
Driver name should report `llvmpipe`.
|
||||
|
||||
### Bundle requirements
|
||||
|
||||
`UNIT_TEST_APP_BUNDLE` must point to a built Flutter bundle. For the parity test any bundle works (it just renders Flutter UI).
|
||||
|
||||
For multi-layer goldens the bundle must construct a `PlatformViewSurface` with one of the registered view types (`layer_playground_view` or `nav_render_view`). Minimal example:
|
||||
|
||||
```dart
|
||||
class GoldenScene extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(children: [
|
||||
Container(color: Colors.green), // layer 0
|
||||
const SizedBox(
|
||||
width: 400, height: 300,
|
||||
child: PlatformViewSurface(
|
||||
viewType: 'layer_playground_view',
|
||||
controller: ..., // layer 1
|
||||
gestureRecognizers: <Factory<...>>{},
|
||||
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 100, left: 100,
|
||||
child: Text('above', style: TextStyle(fontSize: 48)), // layer 2
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Running the parity gate
|
||||
|
||||
Configure with both flags on, point at the bundle:
|
||||
|
||||
```bash
|
||||
cmake -S . -B build-goldens \
|
||||
-DBUILD_UNIT_TESTS=ON \
|
||||
-DBUILD_BACKEND_HEADLESS_EGL=ON \
|
||||
-DBUILD_COMPOSITOR=ON \
|
||||
-DUNIT_TEST_APP_BUNDLE=/path/to/flutter/build/linux/x64/release/bundle \
|
||||
-DBUILD_NUMBER=1
|
||||
cmake --build build-goldens --target homescreen_compositor_headless_golden_ut_test_driver -j$(nproc)
|
||||
ctest --test-dir build-goldens -R compositor_headless_golden -V
|
||||
```
|
||||
|
||||
First run with no committed golden: regenerate.
|
||||
|
||||
```bash
|
||||
cmake -B build-goldens -DUNIT_TEST_SAVE_GOLDENS=ON .
|
||||
ctest --test-dir build-goldens -R compositor_headless_golden -V # writes the golden then fails
|
||||
# Inspect build-goldens/test/unit_test/test_images_golden/*.tga manually.
|
||||
cmake -B build-goldens -DUNIT_TEST_SAVE_GOLDENS=OFF .
|
||||
ctest --test-dir build-goldens -R compositor_headless_golden -V # now passes if pixels match
|
||||
```
|
||||
|
||||
The compare tolerance is per-channel `<= 2` (driver rounding); see `utils_images_are_equal`.
|
||||
|
||||
## Running with sanitizers
|
||||
|
||||
```bash
|
||||
cmake -B build-goldens -DSANITIZE_ADDRESS=ON .
|
||||
cmake --build build-goldens --target homescreen_compositor_headless_golden_ut_test_driver -j$(nproc)
|
||||
ctest --test-dir build-goldens -R compositor_headless_golden
|
||||
```
|
||||
|
||||
`-DSANITIZE_THREAD=ON` for TSan.
|
||||
|
||||
## Vulkan goldens with lavapipe
|
||||
|
||||
The `WaylandVulkanBackend` requires a real Wayland compositor (it uses `VK_KHR_wayland_surface` + a swapchain). Lavapipe alone isn't enough — pair it with weston-headless:
|
||||
|
||||
```bash
|
||||
weston --backend=headless-backend.so --width=1920 --height=1080 &
|
||||
WAYLAND_DISPLAY=wayland-1 \
|
||||
VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json \
|
||||
homescreen -b "$BUNDLE" --window-type=BG
|
||||
```
|
||||
|
||||
There is no native headless-Vulkan backend in this repo today (the existing `HeadlessBackend` is OSMesa/EGL only). Adding one would mean a fourth backend implementation along the lines of `WaylandVulkanBackend` but using `VK_EXT_headless_surface` + a memory-image swapchain — substantial, not on this branch. Until then, Vulkan goldens go through the weston-headless pairing above.
|
||||
|
||||
## What's deferred
|
||||
|
||||
- **HeadlessBackend compositor wiring.** `HeadlessBackend::GetCompositorConfig()` returns null callbacks today. `BUILD_COMPOSITOR=ON` with the headless backend currently falls back to direct rendering — i.e. the parity test verifies "compositor build doesn't break Flutter-only frames" but doesn't yet exercise the create/collect/present callbacks. Wiring requires either porting `GlCompositor` / `EglFboBackingStore` to desktop GL headers (OSMesa is desktop GL, not GLES) or templating those primitives over the GL header set.
|
||||
- **Native headless Vulkan backend.** See above.
|
||||
- **Multi-layer goldens.** Need a Dart bundle that emits a `PlatformViewLayer`; the renderer will then exercise `WaylandEglBackend::PresentLayers`'s general path and the new GL-texture composite step from the plugin migrations.
|
||||
|
||||
When those land, the additional test cases drop into `compositor_headless_golden-test/` next to the existing parity test.
|
||||
@@ -72,4 +72,5 @@ add_subdirectory(backing_store_pool-test)
|
||||
add_subdirectory(present_layer_sequencer-test)
|
||||
add_subdirectory(compositor_registry-test)
|
||||
add_subdirectory(mutation_stack-test)
|
||||
add_subdirectory(compositor_headless_golden-test)
|
||||
#add_subdirectory(texture-test)
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# Compositor-mode parity gate against the OSMesa headless backend.
|
||||
# Builds only when both flags are on; the existing test infrastructure
|
||||
# already requires BUILD_BACKEND_HEADLESS_EGL.
|
||||
if (NOT BUILD_COMPOSITOR)
|
||||
return()
|
||||
endif ()
|
||||
if (NOT BUILD_BACKEND_HEADLESS_EGL)
|
||||
return()
|
||||
endif ()
|
||||
|
||||
set(TESTCASE_NAME "homescreen_compositor_headless_golden_ut_test_driver")
|
||||
set(TESTCASE_CC test_case_compositor_headless_golden.cc)
|
||||
list(REMOVE_ITEM TYPICAL_TEST_DEFINITIONS "ENABLE_PLUGIN_URL_LAUNCHER")
|
||||
|
||||
if (UNIT_TEST_SAVE_GOLDENS)
|
||||
list(APPEND TYPICAL_TEST_DEFINITIONS SAVE_IMAGE_FOR_COMPARISON=1)
|
||||
else ()
|
||||
list(APPEND TYPICAL_TEST_DEFINITIONS SAVE_IMAGE_FOR_COMPARISON=0)
|
||||
endif ()
|
||||
|
||||
add_executable(
|
||||
${TESTCASE_NAME}
|
||||
${TYPICAL_TEST_SOURCES}
|
||||
${TESTCASE_CC}
|
||||
)
|
||||
|
||||
add_sanitizers(${TESTCASE_NAME})
|
||||
|
||||
target_compile_definitions(
|
||||
${TESTCASE_NAME}
|
||||
PRIVATE
|
||||
${TYPICAL_TEST_DEFINITIONS}
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
${TESTCASE_NAME}
|
||||
PRIVATE
|
||||
${TYPICAL_TEST_INC_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
${TESTCASE_NAME}
|
||||
PRIVATE
|
||||
gtest_main
|
||||
${TYPICAL_TEST_LINK_LIBS}
|
||||
toolchain::toolchain
|
||||
)
|
||||
|
||||
add_test(
|
||||
NAME ${TESTCASE_NAME}
|
||||
COMMAND ${TESTCASE_NAME}
|
||||
)
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2026 Toyota Connected North America
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Compositor-mode golden frame test running against the OSMesa headless
|
||||
// backend. Same harness as test_case_app_headless.cc but compiled with
|
||||
// BUILD_COMPOSITOR on. Establishes a parity gate: the single-layer fast
|
||||
// path through the compositor must produce the same pixels as the
|
||||
// non-compositor path. Once HeadlessBackend grows full compositor wiring
|
||||
// (currently dormant — see docs/COMPOSITOR_GOLDENS.md) the test will
|
||||
// exercise the create/collect/present callbacks instead of falling
|
||||
// through to the engine's direct render path.
|
||||
//
|
||||
// Multi-layer goldens (PlatformViewLayer interleaving) require a Flutter
|
||||
// bundle that emits PlatformViewSurface widgets — see the doc for the
|
||||
// staging recipe.
|
||||
|
||||
#include <chrono>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
#include "app.h"
|
||||
#include "configuration/configuration.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "logging.h"
|
||||
#include "unit_test_utils.h"
|
||||
|
||||
TEST(CompositorHeadlessGolden, SingleLayerFastPathParity) {
|
||||
constexpr int argc = 3;
|
||||
const char* argv[3] = {"homescreen", "-b", kBundlePath};
|
||||
const auto argv_p = reinterpret_cast<char**>(&argv);
|
||||
|
||||
const auto configs = Configuration::ParseArgcArgv(argc, argv_p);
|
||||
|
||||
App app(configs);
|
||||
int ret = app.Loop();
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(2s);
|
||||
|
||||
do {
|
||||
ret = app.Loop();
|
||||
} while (ret > 0);
|
||||
|
||||
const auto test_filename =
|
||||
utils_get_image_filename(TEST, "compositor_single_layer");
|
||||
const auto golden_filename =
|
||||
utils_get_image_filename(GOLDEN, "compositor_single_layer");
|
||||
|
||||
const int width = static_cast<int>(configs[0].view.width.value_or(1920));
|
||||
const int height = static_cast<int>(configs[0].view.height.value_or(1080));
|
||||
|
||||
#if SAVE_IMAGE_FOR_COMPARISON
|
||||
// First-time generation. Inspect the image manually before committing.
|
||||
utils_write_targa(app.getViewRenderBuf(0), golden_filename, width, height);
|
||||
GTEST_FAIL() << "UNIT_TEST_SAVE_GOLDENS is on; this run only generates the "
|
||||
"golden. Re-run with the option off to compare.";
|
||||
#else
|
||||
utils_write_targa(app.getViewRenderBuf(0), test_filename, width, height);
|
||||
const int images_are_equal = utils_images_are_equal(
|
||||
test_filename, golden_filename, width, height);
|
||||
EXPECT_EQ(images_are_equal, 1)
|
||||
<< "Compositor single-layer output diverged from baseline. "
|
||||
"If the divergence is intentional, regenerate the golden with "
|
||||
"-DUNIT_TEST_SAVE_GOLDENS=ON.";
|
||||
#endif
|
||||
}
|
||||
Vendored
+9
@@ -86,6 +86,12 @@ if (BUILD_UNIT_TESTS)
|
||||
if (TOOLCHAIN_COMPILE_OPTIONS)
|
||||
target_compile_options(gtest PRIVATE ${TOOLCHAIN_COMPILE_OPTIONS})
|
||||
endif ()
|
||||
# GCC 15 promotes a signed-conversion warning inside googletest itself
|
||||
# (gtest.cc: kSize template parameter). Demote to a warning for
|
||||
# third_party only — the project's own Werror settings stay intact.
|
||||
target_compile_options(gtest PRIVATE
|
||||
-Wno-error=sign-conversion
|
||||
-Wno-error=conversion)
|
||||
target_compile_options(gmock PRIVATE -Wno-error=deprecated-copy)
|
||||
|
||||
target_compile_options(gtest_main PRIVATE -include cstdint)
|
||||
@@ -93,6 +99,9 @@ if (BUILD_UNIT_TESTS)
|
||||
if (TOOLCHAIN_COMPILE_OPTIONS)
|
||||
target_compile_options(gtest_main PRIVATE ${TOOLCHAIN_COMPILE_OPTIONS})
|
||||
endif ()
|
||||
target_compile_options(gtest_main PRIVATE
|
||||
-Wno-error=sign-conversion
|
||||
-Wno-error=conversion)
|
||||
|
||||
target_compile_options(gmock PRIVATE -Wno-error=deprecated-copy)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user