跳转到正文
BiliBili ShadowReplay

Testing Guide for bili-shadowreplay

Overview

This guide explains how to run tests for the bili-shadowreplay project and understand the test infrastructure.

Test Structure

src-tauri/
├── crates/
│   ├── recorder/
│   │   ├── src/           # Library code with inline tests
│   │   ├── tests/         # Integration tests
│   │   │   ├── fixtures/  # Test fixtures (JSON, M3U8, etc.)
│   │   │   ├── integration_test.rs
│   │   │   ├── playlist_test.rs
│   │   │   └── helpers_test.rs
│   │   └── Cargo.toml
│   ├── danmu_stream/      # Danmu provider tests
│   ├── ffmpeg_utils/      # Native ffmpeg-next media pipeline and optional CLI oracles;
│   │                      # fixtures in `src-tauri/tests/video/`
│   └── whisper-cpp-rs/    # (Excluded from CI - requires CMake)

Running Tests Locally

Prerequisites

  1. System Dependencies (Ubuntu/Debian):

    sudo apt-get update
    sudo apt-get install -y libssl-dev pkg-config clang libclang-dev \
      libavformat-dev libavcodec-dev libavutil-dev libswresample-dev libavfilter-dev
    # Optional: independent fixture generation and CLI parity oracles.
    sudo apt-get install -y ffmpeg
  2. Rust Toolchain: Install Rust through rustup. The repository's rust-toolchain.toml selects Rust 1.99.0, including rustfmt and Clippy, without changing your global default. From the repository directory, verify (and install if needed):

    rustc --version
    cargo --version
  3. Git Submodules (for full build):

    git submodule update --init --recursive

Quick Test Commands

cd src-tauri

# Run all workspace tests (excluding whisper-cpp-rs)
export PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig
cargo test --workspace --lib --exclude whisper-cpp-rs

# Run recorder package tests only
cargo test --package recorder

# Run specific test file
cargo test --package recorder --test integration_test

# Run with verbose output
cargo test --package recorder -- --nocapture

# Run a single test
cargo test --package recorder test_platform_type_string_conversion

The native media layer requires matching FFmpeg development libraries; see FFmpeg build configuration. To verify metadata and recording behavior:

cargo test --package ffmpeg_utils
cargo test --package recorder --lib core::hls_recorder::tests
cargo test --package bili-shadowreplay ffmpeg::audio
cargo test --package bili-shadowreplay material::tests

These tests use real MP4, WAV, TS and fMP4 fixtures. Native probing does not require ffprobe on PATH; one optional parity test compares fields with ffprobe when available. HLS tests download fixtures from a local HTTP server and verify init-map inheritance, EXTINF accounting, damaged TS appends and stream-shape changes. Native audio tests cover streaming decode/resampling, resampler tail draining, waveform windows, WAV output and contiguous 30-second chunks. Optional CLI comparisons also check compressed-audio PCM output and stream selection. Range extraction comparisons require identical sample counts and PCM samples for AAC (M4A, MP4, FLV, ADTS), MP3 and PCM, covering seek-zero priming, fractional starts, delayed/shifted audio, codec tails, EOF and sample-rate changes. Fixture-based range and cancellation tests run without a CLI; optional oracle tests skip when ffmpeg is unavailable. WAV application tests also check atomic publication and cleanup after cancelling blocked HTTP input. Historical fixture generation and optional packet/pixel parity tests still use the CLI; production paths and fixture-based native tests do not.

For PCM parity tests, put the CLI from the linked SDK on PATH. Different FFmpeg releases can schedule the last audio frame differently; each native result is compared strictly with its matching CLI, without sample-count or PCM tolerances.

Native material Opus tests verify the fixed 16 kHz mono / 64 kbps VBR profile, including floating-point input negotiation, short final frames, Ogg pre-skip and end granules, source timestamps, metadata and atomic output cleanup. Optional comparisons check encoded audio packets and timestamps against the matching CLI; Ogg stream serial numbers are random and are excluded from file comparisons. The linked SDK must provide the libopus encoder and Ogg/Opus muxer.

The 128 kbps upload profile shares this pipeline, retaining source layouts and negotiating supported sample rates. Its CLI comparisons also cover stereo and surround audio, unsupported layouts, unusual sample rates, empty streams and midstream rate/channel changes. Application tests verify the PowerLive call and its subtitle-error mapping without making an external upload.

Preview FLAC tests cover 48 kHz stereo PCM, AAC frame-grid trimming, preroll, adjacent chunks, first-track selection, metadata, EOF and cancellation. Native output uses fixed 4096-sample blocks (with a short final block) on both SDKs. FFmpeg 6.1's old CLI instead repacks into 4608-sample blocks: comparisons retain exact decoded PCM, counts and metadata, plus a separate CLI encode with explicit frame size for strict packet/STREAMINFO comparison. FFmpeg 9 keeps the original command as the strict encoded-output oracle. Nonempty clips below 16 output samples fail consistently with FFmpeg 9; empty clips write headers only.

Audio import/export tests compare copied packets, codec extradata, timestamps, metadata/chapters, M4A faststart and exact converted PCM. AAC256 and MP3 q2 comparisons retain encoded-packet equality, including MP3 Xing/Info headers and LAME delay/padding. The tests distinguish explicit first-track imports from automatic export selection and exercise planar audio across input format changes. Application import/export and WAV chunk tests also execute without a CLI.

JPEG tests use a small checked-in H.264 B-frame clip and generated PPM images. Both quality modes, accurate seeking, display matrices, color range, SAR and automatic video selection are compared byte-for-byte with the matching CLI. On FFmpeg 6, the single-image zero-position oracle omits the broken -ss 0; video seek comparisons retain the original command. Native tests also cover concurrent extraction, atomic publication, EOF fallback and interrupting HTTP I/O.

Image normalization tests compare PNG bytes against the former CLI for alpha, animated PNG/GIF, JPEG, WebP, scaling and ICC profiles on FFmpeg 6 and 9. They also exercise encoded-byte/pixel limits, compressed PNG metadata expansion, deadlines, cancellation and preservation of existing output on failure. Native calls cancel cooperatively; these bounds do not provide a hard per-job memory or time limit.

Single-input media tests compare copied and encoded streams with the matching FFmpeg 6/9 CLI, including packet payloads, codec headers, timestamps, durations, key flags, side data and metadata. Their CLI oracle fixes simple and complex filter graphs to one thread, matching the native pipeline; encoded-byte parity does not assert equivalence with host-dependent automatic filter threading. They cover input-side copy seeks, output-side encoded seeks, negative timestamps, default subtitle selection and explicitly incompatible subtitle copies. Matroska's initially missing B-frame DTS is predicted before copying. Frame scheduling has 25 additional exact framehash cases for CFR/VFR, gaps, initial offsets, EOF tails and trim rounding. Native application tests run without a CLI and check publication before 100% progress, failed-output retention and cancellation of stalled network input.

Concat-demuxer tests compare MP4 and MPEG-TS packets, stream headers, duration hints and the default mux delay on FFmpeg 6/9. The library-only fixture checks coded NALs across both files while accounting for the demuxer's SPS/PPS insertion. Cancellation verifies that network input closes before its worker-owned list is released; application tests also run with an empty PATH and retain prior output when a later input is missing.

Native Skia subtitle tests cover generated styles, color emoji, cue boundaries, preview/export agreement, copied audio and invalid-input output retention. Project tests exercise generated subtitles and danmaku across segment boundaries and invalidate cached segments when subtitle contents or font identity change. External ASS/libass burn-in and its CLI parity tests have been removed.

Shared-graph tests compare decoded concat and xfade/acrossfade against matching FFmpeg 6/9 commands, including mixed 12/24 fps inputs, explicit 30 fps, every coded packet, stream disposition and all copied audio tracks. Library-only tests cover generated color, simultaneous video/WAV outputs, exact PCM, predictable and late publication errors, and resources retained through final publication. An empty-audio atrim/apad graph also checks missing-PTS continuation and terminal filter-chain separators. The raw-frame reader has separate bounded-queue, buffer-ownership, cancellation, EOF/count and downstream-error tests.

The project-render suite now executes the native segment and final-mix paths. Its 39 tests (including the opt-in cases) cover exact frame/sample boundaries, cross-segment audio, overlays, ASS/SRT, cache reuse, cancellation, scratch cleanup and retaining an earlier export after failure. Raw-overlay, SRT and danmaku application tests also run with libraries alone. FFmpeg 6/9 alpha oracles compare every BGRA byte over three base colors; streaming tests verify reuse of the bounded producer buffers and preserve copied audio.

Native FLV-to-HLS tests use a six-second checked-in audiovisual fixture. They compare the playlist, segment durations and every copied packet with the CLI, including timestamps, and verify append behavior without replacing old segments. Local HTTP tests check headers, idle timeout, graceful input stop with final-tail publication, and cancellation without a surviving writer. Recorder tests verify ENDLIST and complete final duration/size updates without invoking a CLI.

To smoke-test Douyu against a currently live numeric room using the production DouyuRecorder loop for five minutes (requires the matching native media libraries and network access):

cd src-tauri
DOUYU_TEST_ROOM_ID=1667826 cargo test -p recorder live_room_records_continuously_for_five_minutes --lib -- --ignored --nocapture

The test is ignored in normal runs because the room and CDN must be reachable. It requires one archive and roughly one minute of new HLS segments each minute, and removes the temporary recording when the test finishes. Replace 1667826 if it is offline.

Windows native media validation

Use an x64 MSVC developer PowerShell and the pinned Gyan FFmpeg 9.0.2 shared SDK. Run setup and Cargo in the same shell: ffmpeg_setup.ps1 sets FFMPEG_DIR and the SDK DLL/optional CLI search path for that process. It locates LIBCLANG_PATH when LLVM is available; otherwise set it to LLVM's bin directory. The Skia preparation script prints its URL; explicitly set SKIA_BINARIES_URL to that value, preserving the {key} placeholder. Windows application builds include the Vulkan backend by default, including headless builds; the preparation script now selects the Vulkan archive without an extra option. Re-run it if the environment previously prepared a CPU archive.

From the repository root:

& .\ffmpeg_setup.ps1
rustup component add llvm-tools
$skia = node scripts/prepare-windows-skia.mjs
$env:SKIA_BINARIES_URL = ($skia | Where-Object { $_ -like 'SKIA_BINARIES_URL=*' }) -replace '^SKIA_BINARIES_URL=', ''
cargo test --manifest-path src-tauri/Cargo.toml -p ffmpeg_utils --locked --lib
cargo test --manifest-path src-tauri/Cargo.toml -p bili-shadowreplay --locked ffmpeg:: -- --include-ignored --test-threads=1 --skip native_gpu_
cargo test --manifest-path src-tauri/Cargo.toml -p recorder --locked

# On a Vulkan Video-capable device: actual production GPU exports, no feature opt-in.
cargo test --manifest-path src-tauri/Cargo.toml --locked --no-default-features --features headless native_gpu_ -- --ignored --nocapture --test-threads=1

# Targeted GOP check: both CLI and native plans must retain 301 frames and GOP <=120.
cargo test --manifest-path src-tauri/Cargo.toml -p bili-shadowreplay --locked encoded_keyframe_gaps_respect_the_120_frame_gop -- --include-ignored

The GOP test covers both priorities for software encoders and available NVENC/VideoToolbox encoders. Both paths require a first keyframe at zero, gaps and a final tail of at most 120 frames, and at least one exact 120-frame gap. Its 320x240 fixture matches the native probe's supported geometry. For deployment checks, place the core test executable beside the packaged SDK DLLs, then remove SDK variables and SDK/CLI directories from PATH. Native media tests must still pass; optional CLI oracles skip when executables are absent. Check that packaged DLLs match the build SDK and that the installer contains no FFmpeg CLI executables. Package tests do not replace an interactive GUI smoke test.

Running All Tests (including integration)

cd src-tauri
export PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig

# Library tests
cargo test --workspace --lib --exclude whisper-cpp-rs

# Integration tests
cargo test --package recorder --test '*'

Test Coverage

Current Coverage (as of 2026-09-18)

ComponentStatusNotes
Platform adapters⚠️ PartialBasic parsing tests, no full mock integration yet
Recorder core✅ GoodPlaylist, HLS recorder internals
Pure utilities✅ ExcellentPlatform type, sanitize filename, etc.
Danmu providers⚠️ LimitedBasic tests, mostly inline
FFmpeg integration✅ Probe coverageNative metadata parity, fMP4 AVIO and real HLS segment processing
End-to-end flows❌ NoneFuture work

Total: ~91 library tests passing (live-network smoke tests are #[ignore])

Test Infrastructure

HTTP Mocking

We use wiremock to mock HTTP calls for platform APIs:

use wiremock::{MockServer, Mock, ResponseTemplate};
use wiremock::matchers::{method, path};

#[tokio::test]
async fn test_with_mock_server() {
    let mock_server = MockServer::start().await;

    Mock::given(method("GET"))
        .and(path("/api/endpoint"))
        .respond_with(ResponseTemplate::new(200).set_body_string("{}"))
        .mount(&mock_server)
        .await;

    // Point adapter helpers like get_room_info_with_base(..., &mock_server.uri()) here
}

Test Fixtures

Fixtures are stored in src-tauri/crates/recorder/tests/fixtures/. They are real API/page responses captured with the same request shapes as recorder::platforms::*::api (short-lived query tokens are redacted).

Refresh them locally:

export TEST_BILIBILI_COOKIE='...'
export TEST_DOUYIN_COOKIE='...'
python3 scripts/fetch_test_fixtures.py

Included fixtures:

  • bilibili_room_info.json / bilibili_play_url.json / bilibili_user_info.json
  • douyin_room_info.json / douyin_room_offline.json (H5 reflow API)
  • huya_room_page.html (m.huya.com + HNF_GLOBAL_INIT)
  • kuaishou_initial_state.json / kuaishou_room_*.json
  • test_playlist.m3u8 (real Bilibili fmp4 HLS snippet)

Load fixtures in tests:

fn load_fixture(name: &str) -> String {
    let fixture_path = format!("{}/tests/fixtures/{}", env!("CARGO_MANIFEST_DIR"), name);
    std::fs::read_to_string(fixture_path).unwrap()
}

macOS note: if linking fails with unknown architecture arm64e.x1-macos, point Cargo at an older SDK, e.g. export SDKROOT=/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk.

Continuous Integration

Tests run automatically on GitHub Actions for:

  • Push to main branch
  • Pull requests to main

See .github/workflows/test.yml for the CI configuration.

CI Workflow

  1. Checkout code with submodules
  2. Install system dependencies (OpenSSL, pkg-config)
  3. Setup Rust toolchain
  4. Cache dependencies
  5. Run workspace library tests (cargo test --workspace --lib --exclude whisper-cpp-rs)
  6. Run recorder integration tests (cargo test --package recorder --tests)

Note: Tests that require CUDA, Whisper models, or long FFmpeg encodes are excluded from CI. Live-network platform smoke tests are marked #[ignore] and must be run manually with cargo test -- --ignored.

Known Ignored Live Tests

These are optional smoke tests against real platforms (room availability / anti-bot vary):

  1. platforms::huya::api::tests::test_get_room_info_live / test_get_user_info
  2. platforms::douyin::api::tests::test_get_room_owner_sec_uid_live

CI uses fixture-backed or parse-only coverage instead.

Writing New Tests

Unit Tests (Inline)

// In src-tauri/crates/recorder/src/my_module.rs
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_my_function() {
        assert_eq!(my_function(5), 10);
    }
}

Integration Tests

// In src-tauri/crates/recorder/tests/my_test.rs
use recorder::core::playlist::HlsPlaylist;

#[tokio::test]
async fn test_integration_scenario() {
    // Test code here
}

Test Guidelines

  1. Use fixtures for external API responses (never hit production APIs in tests)
  2. Mock HTTP calls with wiremock for platform adapter tests
  3. Clean up temp files in tests (tokio::fs::remove_file after test)
  4. Test error paths not just happy paths
  5. Keep tests fast - CI should complete in <5 minutes
  6. No flaky tests - tests must be deterministic

What's NOT Tested Yet

The following areas need test coverage in future work:

  1. Platform adapter full integration

    • Complete mock-based tests for all 8 platforms
    • Status transition logic (not live → live → recording → ended)
    • Reconnection and error recovery
  2. FFmpeg operations

    • Successful hardware encoding on physical devices beyond the available test host
    • Longer network interruption/reconnect and codec-change recording soaks
    • Release installation on Windows/Linux after native library packaging changes
  3. End-to-end recording flows

    • Would require long-running integration tests
    • Could use small video fixtures + local HTTP server
  4. Frontend tests

    • Svelte component tests
    • invoker.ts integration tests

Troubleshooting

"Could not find openssl"

export PKG_CONFIG_PATH=/usr/lib/x86_64-linux-gnu/pkgconfig
# Or install: sudo apt-get install libssl-dev pkg-config

"Whisper.cpp CMakeLists.txt not found"

git submodule update --init --recursive
# Or exclude whisper-cpp-rs: cargo test --exclude whisper-cpp-rs

Test Times Out

Some tests create tokio runtimes - ensure you're using #[tokio::test] for async tests.

Resources

Contact

For test infrastructure questions, open an issue.

macOS 开发启动运行库

node --test scripts/tests/macos-runtime.test.mjs 覆盖 SDK 显式配置、下载中断和进程退出后的续传、并发下载隔离、校验失败清理、运行库签名和 FFmpeg 裸文件名加载、离线缓存复用及损坏缓存拒绝。HTTP 测试通过本机临时服务验证 curl 的 Range 重试,以及不支持续传和 HTTP 416 时的恢复;不访问外网,需要允许监听回环地址。动态库加载测试仅在 macOS 执行。完整开发启动验证应清除手工 FFMPEG_DIR、PKG_CONFIG_PATH、BSR_VULKAN_LIBRARY、BSR_MOLTENVK_LIBRARY 和 DYLD_* 配置,通过 yarn tauri dev 确认自动准备,并执行 GPU 导出探测;不要用此前测试目录里的运行库代替首次准备流程。

Windows 开发启动依赖

node --test scripts/tests/windows-runtime.test.mjs scripts/tests/prepare-windows-skia.test.mjs 覆盖 Windows 环境变量大小写、开发者环境解析、自定义 FFmpeg SDK、DLL 部署与重复启动、SDK 缺失文件拒绝、Skia 缓存版本及校验。PowerShell 集成测试仅在 Windows 运行,不联网。实机验证应在普通终端清除手动 FFMPEG_DIR、SKIA_BINARIES_URL、LIBCLANG_PATH 和 VS 开发环境,确认首次准备、缓存复用、yarn tauri dev 构建及原生 GPU 导出探测;使用独立应用数据,避免触碰现有录像和账号。

本页内容