mirror of
https://github.com/thinkonmay/sunshine-sdk.git
synced 2026-08-06 20:54:52 +00:00
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:
parent
5195bd19e2
commit
eaf972ddce
46
.github/workflows/ci.yml
vendored
Normal file
46
.github/workflows/ci.yml
vendored
Normal 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
|
||||
@ -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
16
Makefile
Normal 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
82
ci/README.md
Normal 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
67
ci/lib.sh
Executable 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
102
ci/local.ps1
Normal 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
92
ci/local.sh
Executable 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"
|
||||
@ -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"
|
||||
|
||||
@ -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()
|
||||
@ -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);
|
||||
|
||||
@ -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
69
src/ivshmem_protocol.h
Normal 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
27
tests/CMakeLists.txt
Normal 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
|
||||
)
|
||||
28
tests/standalone/CMakeLists.txt
Normal file
28
tests/standalone/CMakeLists.txt
Normal 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)
|
||||
117
tests/unit/test_ivshmem_protocol.cpp
Normal file
117
tests/unit/test_ivshmem_protocol.cpp
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user