Add IVSHMEM protocol unit tests and Windows CI pipeline.

Introduce testable protocol helpers, GoogleTest coverage, and ci/local.sh
mirroring the sunshine-sdk GitHub Actions workflow.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pigeatgarlic 2026-06-18 07:37:18 +07:00
parent 5195bd19e2
commit eaf972ddce
15 changed files with 655 additions and 15 deletions

46
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,46 @@
---
# Unit tests + build for the sunshine-sdk repository.
# Local equivalent: ./ci/local.sh [all]
name: CI
on:
push:
pull_request:
workflow_dispatch:
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: CI (windows-amd64)
runs-on: windows-2025
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: true
install: >-
git
mingw-w64-x86_64-boost
mingw-w64-x86_64-cmake
mingw-w64-x86_64-cppwinrt
mingw-w64-x86_64-miniupnpc
mingw-w64-x86_64-nlohmann-json
mingw-w64-x86_64-onevpl
mingw-w64-x86_64-openssl
mingw-w64-x86_64-opus
mingw-w64-x86_64-toolchain
- name: Run CI
shell: msys2 {0}
run: |
chmod +x ./ci/local.sh
./ci/local.sh all

View File

@ -8,6 +8,8 @@ project(Sunshine VERSION 0.23.1
DESCRIPTION "Self-hosted game stream host for Moonlight"
HOMEPAGE_URL "https://app.lizardbyte.dev/Sunshine")
include(CTest)
set(PROJECT_LICENSE "GPL-3.0")
set(PROJECT_LONG_DESCRIPTION "Offering low latency, cloud gaming server capabilities with support for AMD, Intel, \

16
Makefile Normal file
View File

@ -0,0 +1,16 @@
.PHONY: ci ci-all ci-test ci-build configure
ci:
./ci/local.sh
ci-all:
./ci/local.sh all
ci-test:
./ci/local.sh test
ci-build:
./ci/local.sh build
configure:
./ci/local.sh configure

82
ci/README.md Normal file
View File

@ -0,0 +1,82 @@
# Local CI — `worker/sunshine`
Run the same checks as GitHub Actions **before pushing**, without waiting on remote runners.
**Platform:** Windows MSYS2 MINGW64 for full `sunshine.exe` build. **Unit tests** also run on macOS/Linux via `tests/standalone/` (no FFmpeg/submodules required).
Mandatory testing rules: [docs/engineering/ci_cd/testing_policy.md](../../../docs/engineering/ci_cd/testing_policy.md).
## Quick start
```bash
cd worker/sunshine
git submodule update --init --recursive
./ci/local.sh test
```
```powershell
cd worker\sunshine
git submodule update --init --recursive
.\ci\local.ps1 -Stage test
```
Default pipeline (`./ci/local.sh`):
1. Init submodules (`third-party/build-deps`, `third-party/nvapi-open-source-sdk`) if missing
2. `cmake -DBUILD_TESTS=ON` + Ninja
3. Build and run `test_ivshmem_protocol` (GoogleTest)
4. Build `sunshine.exe`
## GitHub Actions
| Location | Workflow |
|----------|----------|
| Sunshine repo | [`.github/workflows/ci.yml`](../.github/workflows/ci.yml) |
| Thinkmay monorepo | [`window.yml`](../../../.github/workflows/window.yml) job `build_sunshine` |
Both run unit tests via `ctest` / `test_ivshmem_protocol` before packaging.
## Stages
| Stage | Command | Purpose |
|-------|---------|---------|
| **default** | `./ci/local.sh` | Unit tests + build |
| **all** | `./ci/local.sh all` | Same as default |
| **test** | `./ci/local.sh test` | IVSHMEM protocol unit tests only |
| **build** | `./ci/local.sh build` | Compile `sunshine.exe` |
| **configure** | `./ci/local.sh configure` | CMake configure only |
PowerShell: `.\ci\local.ps1 -Stage test`
## Environment flags
| Variable / switch | Effect |
|-------------------|--------|
| `SKIP_BUILD=1` / `-SkipBuild` | Skip `sunshine.exe` compile |
| `SKIP_SUBMODULES=1` / `-SkipSubmodules` | Skip `git submodule update` |
| `BUILD_DIR=build` | CMake build directory |
## Test scope (CI)
| Tier | Target | Notes |
|------|--------|-------|
| **CI** | `test_ivshmem_protocol` | Queue sizes, packet append, Sunshine video wire header, ring indices (Windows full build + macOS/Linux standalone) |
| **CI (monorepo Go)** | `worker/proxy/util/memory`, `worker/daemon/utils/memory` | Host-side IVSHMEM protocol tests in Go |
| **Lab** | DXGI capture, NVENC encode, IVSHMEM driver | Not in default CI |
## Dependencies
MSYS2 MINGW64 packages (same as monorepo `window.yml`):
```bash
pacman -S mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja \
mingw-w64-x86_64-boost mingw-w64-x86_64-openssl mingw-w64-x86_64-opus \
mingw-w64-x86_64-miniupnpc mingw-w64-x86_64-nlohmann-json mingw-w64-x86_64-onevpl
```
## Make shortcut
```bash
make ci
make test
```

67
ci/lib.sh Executable file
View File

@ -0,0 +1,67 @@
#!/usr/bin/env bash
# Shared helpers for worker/sunshine local CI (sourced, not executed directly).
set -euo pipefail
ci_log() {
printf '\n==> %s\n' "$*"
}
ci_os() {
uname -s
}
ci_arch() {
uname -m
}
ci_is_msys() {
case "$(ci_os)" in
MINGW*|MSYS*) return 0 ;;
*) return 1 ;;
esac
}
ci_require_windows() {
if ! ci_is_msys; then
echo "sunshine CI requires Windows MSYS2 MINGW64 (see ci/README.md)" >&2
return 1
fi
}
ci_init_submodules() {
if [[ ! -f third-party/build-deps/dist/Windows-AMD64/lib/libavcodec.a ]]; then
ci_log "initializing git submodules (build-deps + nvapi)"
git submodule update --init --recursive
fi
}
ci_cmake_configure() {
local build_dir="${1:-build}"
local build_type="${2:-Release}"
mkdir -p "${build_dir}"
cmake -S . -B "${build_dir}" \
-DCMAKE_BUILD_TYPE="${build_type}" \
-G Ninja \
-DBUILD_TESTS=ON
}
ci_run_standalone_tests() {
local build_dir="${1:-build-standalone}"
ci_log "standalone IVSHMEM protocol tests (non-Windows)"
cmake -S tests/standalone -B "${build_dir}"
cmake --build "${build_dir}"
"${build_dir}/test_ivshmem_protocol"
}
ci_run_tests() {
local build_dir="${1:-build}"
if [[ -x "${build_dir}/tests/test_ivshmem_protocol" ]]; then
"${build_dir}/tests/test_ivshmem_protocol"
return $?
fi
if [[ -x "${build_dir}/tests/test_ivshmem_protocol.exe" ]]; then
"${build_dir}/tests/test_ivshmem_protocol.exe"
return $?
fi
(cd "${build_dir}" && ctest --output-on-failure -R ivshmem_protocol)
}

102
ci/local.ps1 Normal file
View File

@ -0,0 +1,102 @@
# Local CI for worker/sunshine — mirrors .github/workflows/ci.yml
param(
[ValidateSet('default', 'all', 'test', 'build', 'configure')]
[string]$Stage = 'default',
[switch]$SkipBuild,
[switch]$SkipSubmodules,
[string]$BuildDir = 'build'
)
$ErrorActionPreference = 'Stop'
$Root = Split-Path -Parent $PSScriptRoot
Set-Location $Root
function Write-Step([string]$Message) {
Write-Host ""
Write-Host "==> $Message"
}
function Ensure-Msys2 {
if (-not (Get-Command gcc -ErrorAction SilentlyContinue)) {
throw "MSYS2 MINGW64 toolchain not found. Open an MSYS2 MINGW64 shell or add gcc to PATH."
}
}
function Initialize-Submodules {
if ($SkipSubmodules) { return }
$marker = Join-Path $Root 'third-party/build-deps/dist/Windows-AMD64/lib/libavcodec.a'
if (-not (Test-Path $marker)) {
Write-Step 'initializing git submodules'
git submodule update --init --recursive
}
}
function Invoke-Configure {
Ensure-Msys2
Initialize-Submodules
Write-Step 'cmake configure'
New-Item -ItemType Directory -Force -Path $BuildDir | Out-Null
cmake -S . -B $BuildDir -DCMAKE_BUILD_TYPE=Release -G Ninja -DBUILD_TESTS=ON
}
function Invoke-Tests {
if (-not (Get-Command gcc -ErrorAction SilentlyContinue) -and -not (Get-Command clang++ -ErrorAction SilentlyContinue)) {
throw "A C++ compiler is required for unit tests."
}
$isMsys = $env:MSYSTEM -match 'MINGW|MSYS'
if (-not $isMsys) {
Write-Step 'standalone IVSHMEM protocol tests (non-Windows)'
$standalone = "${BuildDir}-standalone"
cmake -S tests/standalone -B $standalone
cmake --build $standalone
& (Join-Path $standalone 'test_ivshmem_protocol.exe')
if (-not $?) { & (Join-Path $standalone 'test_ivshmem_protocol') }
return
}
if (-not (Test-Path (Join-Path $BuildDir 'build.ninja'))) {
Invoke-Configure
} else {
Write-Step 'build unit tests'
cmake --build $BuildDir --target test_ivshmem_protocol
}
Write-Step 'run IVSHMEM protocol unit tests'
$exe = Join-Path $BuildDir 'tests/test_ivshmem_protocol.exe'
if (-not (Test-Path $exe)) {
$exe = Join-Path $BuildDir 'tests/test_ivshmem_protocol'
}
if (-not (Test-Path $exe)) {
Push-Location $BuildDir
ctest --output-on-failure -R ivshmem_protocol
Pop-Location
return
}
& $exe
if ($LASTEXITCODE -ne 0) { throw "unit tests failed with exit code $LASTEXITCODE" }
}
function Invoke-Build {
if ($env:MSYSTEM -notmatch 'MINGW|MSYS') {
Write-Step 'skip sunshine.exe build on non-Windows'
return
}
Ensure-Msys2
if ($SkipBuild) {
Write-Step 'skip build (SkipBuild)'
return
}
if (-not (Test-Path (Join-Path $BuildDir 'build.ninja'))) {
Invoke-Configure
}
Write-Step 'build sunshine'
cmake --build $BuildDir --target sunshine
}
switch ($Stage) {
'configure' { Invoke-Configure }
'test' { Invoke-Tests }
'build' { Invoke-Configure; Invoke-Build }
'all' { Invoke-Tests; Invoke-Build }
default { Invoke-Tests; Invoke-Build }
}
Write-Step 'local CI finished OK'

92
ci/local.sh Executable file
View File

@ -0,0 +1,92 @@
#!/usr/bin/env bash
# Local CI for worker/sunshine — mirrors .github/workflows/ci.yml
#
# Usage:
# ./ci/local.sh # configure + unit tests + build sunshine
# ./ci/local.sh test # unit tests only
# ./ci/local.sh build # build sunshine.exe only
# ./ci/local.sh all # test + build (same as default)
#
# Options (env):
# SKIP_BUILD=1 skip sunshine.exe compile
# SKIP_SUBMODULES=1 skip git submodule init
# BUILD_DIR=build cmake build directory
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "${ROOT}"
# shellcheck source=ci/lib.sh
source "${ROOT}/ci/lib.sh"
STAGE="${1:-default}"
BUILD_DIR="${BUILD_DIR:-build}"
step_submodules() {
if [[ "${SKIP_SUBMODULES:-0}" == "1" ]]; then
return 0
fi
ci_init_submodules
}
step_configure() {
ci_require_windows
step_submodules
ci_log "cmake configure"
ci_cmake_configure "${BUILD_DIR}"
}
step_tests() {
if ! ci_is_msys; then
ci_run_standalone_tests "${BUILD_DIR}-standalone"
return $?
fi
if [[ ! -f "${BUILD_DIR}/build.ninja" ]]; then
step_configure
else
ci_log "build unit tests"
cmake --build "${BUILD_DIR}" --target test_ivshmem_protocol
fi
ci_log "run IVSHMEM protocol unit tests"
ci_run_tests "${BUILD_DIR}"
}
step_build() {
if ! ci_is_msys; then
ci_log "skip sunshine.exe build on $(ci_os) (Windows only)"
return 0
fi
if [[ "${SKIP_BUILD:-0}" == "1" ]]; then
ci_log "skip build (SKIP_BUILD=1)"
return 0
fi
if [[ ! -f "${BUILD_DIR}/build.ninja" ]]; then
step_configure
fi
ci_log "build sunshine"
cmake --build "${BUILD_DIR}" --target sunshine
}
run_default() {
step_tests
step_build
}
run_all() {
run_default
}
case "${STAGE}" in
default|"") run_default ;;
all) run_all ;;
test) step_tests ;;
build) step_configure; step_build ;;
configure) step_configure ;;
*)
echo "unknown stage: ${STAGE}" >&2
echo "usage: $0 [default|all|test|build|configure]" >&2
exit 2
;;
esac
ci_log "local CI finished OK"

View File

@ -44,6 +44,7 @@ include_directories("${CMAKE_CURRENT_BINARY_DIR}") # required for importing ver
set(SUNSHINE_TARGET_FILES
"${CMAKE_SOURCE_DIR}/src/smemory.h"
"${CMAKE_SOURCE_DIR}/src/ivshmem_protocol.h"
"${CMAKE_SOURCE_DIR}/src/interprocess.h"
"${CMAKE_SOURCE_DIR}/src/interprocess.cpp"
"${CMAKE_SOURCE_DIR}/src/cbs.cpp"

View File

@ -29,4 +29,7 @@ endif()
target_compile_options(sunshine PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${SUNSHINE_COMPILE_OPTIONS}>;$<$<COMPILE_LANGUAGE:CUDA>:${SUNSHINE_COMPILE_OPTIONS_CUDA};-std=c++17>) # cmake-lint: disable=C0301
# custom compile flags, must be after adding tests
option(BUILD_TESTS "Build IVSHMEM protocol unit tests" ON)
if(BUILD_TESTS)
add_subdirectory(tests)
endif()

View File

@ -237,15 +237,6 @@ bool IVSHMEM::RingDoorbell(UINT16 peerID, UINT16 vector) {
return true;
}
void copy_to_packet(MediaPacket *packet, void *data, size_t size) {
memcpy(packet->data + packet->size, data, size);
packet->size += size;
}
void copy_to_dpacket(DataPacket *packet, void *data, size_t size) {
memcpy(packet->data + packet->size, data, size);
packet->size += size;
}
SharedMemory::SharedMemory(const char *name, size_t size)
: m_size(size), m_handle(NULL), m_memory(NULL), m_initialized(false) {
memset(m_name, 0, 512);

View File

@ -19,6 +19,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#pragma once
#include "ivshmem_protocol.h"
#include "smemory.h"
#include <stdbool.h>
#include <windows.h>
@ -65,8 +66,4 @@ private:
HANDLE m_handle;
void *m_memory;
bool m_initialized;
};
void copy_to_packet(MediaPacket *packet, void *data, size_t size);
void copy_to_dpacket(DataPacket *packet, void *data, size_t size);
};

69
src/ivshmem_protocol.h Normal file
View File

@ -0,0 +1,69 @@
#pragma once
/**
* @file src/ivshmem_protocol.h
* @brief Pure IVSHMEM queue/packet helpers shared by guest encode and host consumers.
*
* Layout must stay in sync with worker/proxy/util/memory and worker/daemon/utils/memory
* CGO definitions and docs/engineering/ci_cd/sunshine_testing_feasibility.md.
*/
#include "smemory.h"
#include <cstddef>
#include <cstdint>
#include <cstring>
namespace ivshmem_protocol {
constexpr int kInQueueSize = IN_QUEUE_SIZE;
constexpr int kOutQueueSize = OUT_QUEUE_SIZE;
constexpr int kMaxDisplay = MAX_DISPLAY;
constexpr std::size_t kMediaPacketSize = MEDIA_PACKET_SIZE;
constexpr std::size_t kDataPacketSize = DATA_PACKET_SIZE;
constexpr std::size_t kTagSize = TAG_SIZE;
/** Video packet header written by shmsunshine before encoded payload. */
constexpr std::size_t kVideoPacketHeaderSize =
sizeof(std::uint64_t) + sizeof(std::uint64_t) + sizeof(std::uint8_t);
inline int advance_index(int index, int capacity) {
int updated = index + 1;
if (updated >= capacity) {
return 0;
}
return updated;
}
inline void reset_packet(MediaPacket *packet) {
packet->size = 0;
}
inline void reset_packet(DataPacket *packet) {
packet->size = 0;
}
inline void append_to_packet(MediaPacket *packet, const void *data, std::size_t size) {
std::memcpy(packet->data + packet->size, data, size);
packet->size += static_cast<int>(size);
}
inline void append_to_packet(DataPacket *packet, const void *data, std::size_t size) {
std::memcpy(packet->data + packet->size, data, size);
packet->size += static_cast<int>(size);
}
inline bool video_payload_fits(std::size_t payload_size) {
return payload_size <= (kMediaPacketSize - kVideoPacketHeaderSize);
}
} // namespace ivshmem_protocol
// Legacy C linkage used by interprocess.h and main.cpp.
inline void copy_to_packet(MediaPacket *packet, void *data, size_t size) {
ivshmem_protocol::append_to_packet(packet, data, size);
}
inline void copy_to_dpacket(DataPacket *packet, void *data, size_t size) {
ivshmem_protocol::append_to_packet(packet, data, size);
}

27
tests/CMakeLists.txt Normal file
View File

@ -0,0 +1,27 @@
set(SUNSHINE_SRC_ROOT "${CMAKE_CURRENT_LIST_DIR}/..")
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
set(INSTALL_GMOCK OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/refs/tags/v1.15.2.zip
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
FetchContent_MakeAvailable(googletest)
add_executable(test_ivshmem_protocol
unit/test_ivshmem_protocol.cpp
)
target_include_directories(test_ivshmem_protocol PRIVATE "${SUNSHINE_SRC_ROOT}/src")
target_link_libraries(test_ivshmem_protocol PRIVATE GTest::gtest_main)
add_test(NAME ivshmem_protocol COMMAND test_ivshmem_protocol)
set_tests_properties(ivshmem_protocol PROPERTIES
LABELS "unit;ivshmem"
TIMEOUT 120
)

View File

@ -0,0 +1,28 @@
cmake_minimum_required(VERSION 3.18)
project(sunshine_protocol_tests LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
set(INSTALL_GMOCK OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/refs/tags/v1.15.2.zip
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
FetchContent_MakeAvailable(googletest)
enable_testing()
set(SUNSHINE_SRC_ROOT "${CMAKE_CURRENT_LIST_DIR}/../..")
add_executable(test_ivshmem_protocol
../unit/test_ivshmem_protocol.cpp
)
target_include_directories(test_ivshmem_protocol PRIVATE "${SUNSHINE_SRC_ROOT}/src")
target_link_libraries(test_ivshmem_protocol PRIVATE GTest::gtest_main)
add_test(NAME ivshmem_protocol COMMAND test_ivshmem_protocol)

View File

@ -0,0 +1,117 @@
#include <gtest/gtest.h>
#include "ivshmem_protocol.h"
#include "smemory.h"
#include <array>
#include <cstdint>
#include <cstring>
#include <memory>
#include <vector>
namespace {
TEST(IvshmemProtocolConstants, QueueAndPacketSizes) {
EXPECT_EQ(ivshmem_protocol::kInQueueSize, 8);
EXPECT_EQ(ivshmem_protocol::kOutQueueSize, 8);
EXPECT_EQ(ivshmem_protocol::kMaxDisplay, 3);
EXPECT_EQ(ivshmem_protocol::kMediaPacketSize, 5u * 1024u * 1024u);
EXPECT_EQ(ivshmem_protocol::kDataPacketSize, 5u * 1024u);
EXPECT_EQ(ivshmem_protocol::kTagSize, 32u * 1024u);
EXPECT_EQ(ivshmem_protocol::kVideoPacketHeaderSize, 17u);
}
TEST(IvshmemProtocolConstants, StructLayout) {
EXPECT_EQ(sizeof(MediaPacket::data), MEDIA_PACKET_SIZE);
EXPECT_EQ(sizeof(DataPacket::data), DATA_PACKET_SIZE);
EXPECT_EQ(sizeof(DisplayQueue::metadata), sizeof(QueueMetadata));
EXPECT_GT(sizeof(MediaMemory), sizeof(DisplayQueue) * MAX_DISPLAY);
}
TEST(IvshmemProtocolQueue, AdvanceIndexWraps) {
EXPECT_EQ(ivshmem_protocol::advance_index(0, IN_QUEUE_SIZE), 1);
EXPECT_EQ(ivshmem_protocol::advance_index(IN_QUEUE_SIZE - 1, IN_QUEUE_SIZE), 0);
}
TEST(IvshmemProtocolPacket, AppendMediaPacketSegments) {
auto packet = std::make_unique<MediaPacket>();
ivshmem_protocol::reset_packet(packet.get());
std::uint64_t findex = 42;
std::uint64_t duration = 3000;
std::uint8_t flags = 0x3;
const char payload[] = "encoded-nal-unit";
ivshmem_protocol::append_to_packet(packet.get(), &findex, sizeof(findex));
ivshmem_protocol::append_to_packet(packet.get(), &duration, sizeof(duration));
ivshmem_protocol::append_to_packet(packet.get(), &flags, sizeof(flags));
ivshmem_protocol::append_to_packet(packet.get(), payload, sizeof(payload) - 1);
ASSERT_EQ(packet->size, 17 + static_cast<int>(sizeof(payload) - 1));
std::uint64_t out_findex {};
std::uint64_t out_duration {};
std::uint8_t out_flags {};
std::memcpy(&out_findex, packet->data, sizeof(out_findex));
std::memcpy(&out_duration, packet->data + sizeof(out_findex), sizeof(out_duration));
std::memcpy(&out_flags, packet->data + sizeof(out_findex) + sizeof(out_duration), sizeof(out_flags));
EXPECT_EQ(out_findex, findex);
EXPECT_EQ(out_duration, duration);
EXPECT_EQ(out_flags, flags);
EXPECT_STREQ(packet->data + 17, payload);
}
TEST(IvshmemProtocolPacket, LegacyCopyHelpersMatchAppend) {
auto media = std::make_unique<MediaPacket>();
auto data = std::make_unique<DataPacket>();
const char chunk[] = "ivshmem";
copy_to_packet(media.get(), (void *)chunk, sizeof(chunk) - 1);
ivshmem_protocol::append_to_packet(data.get(), chunk, sizeof(chunk) - 1);
EXPECT_EQ(media->size, static_cast<int>(sizeof(chunk) - 1));
EXPECT_EQ(data->size, static_cast<int>(sizeof(chunk) - 1));
EXPECT_EQ(std::memcmp(media->data, chunk, sizeof(chunk) - 1), 0);
EXPECT_EQ(std::memcmp(data->data, chunk, sizeof(chunk) - 1), 0);
}
TEST(IvshmemProtocolPacket, VideoPayloadCapacityGuard) {
EXPECT_TRUE(ivshmem_protocol::video_payload_fits(MEDIA_PACKET_SIZE -
ivshmem_protocol::kVideoPacketHeaderSize));
EXPECT_FALSE(ivshmem_protocol::video_payload_fits(MEDIA_PACKET_SIZE));
}
TEST(IvshmemProtocolQueue, MediaQueueRingUsesIncomingIndex) {
// MediaQueue is ~40 MiB; keep it off the stack.
auto queue = std::make_unique<MediaQueue>();
queue->inindex = 0;
queue->outindex = 0;
for (int i = 0; i < IN_QUEUE_SIZE - 1; ++i) {
ivshmem_protocol::reset_packet(&queue->incoming[queue->inindex]);
const std::uint64_t frame = static_cast<std::uint64_t>(i);
ivshmem_protocol::append_to_packet(&queue->incoming[queue->inindex], &frame, sizeof(frame));
queue->inindex = ivshmem_protocol::advance_index(queue->inindex, IN_QUEUE_SIZE);
}
EXPECT_EQ(queue->inindex, IN_QUEUE_SIZE - 1);
std::vector<std::uint64_t> seen;
int read_index = 0;
for (int i = 0; i < IN_QUEUE_SIZE - 1; ++i) {
const auto &block = queue->incoming[read_index];
std::uint64_t frame {};
std::memcpy(&frame, block.data, sizeof(frame));
seen.push_back(frame);
read_index = ivshmem_protocol::advance_index(read_index, IN_QUEUE_SIZE);
}
ASSERT_EQ(seen.size(), static_cast<std::size_t>(IN_QUEUE_SIZE - 1));
for (int i = 0; i < IN_QUEUE_SIZE - 1; ++i) {
EXPECT_EQ(seen[i], static_cast<std::uint64_t>(i));
}
}
} // namespace