Skip to content

e2e 补 TCP echo:ecm/rndis 连设备侧 5000 端口发 5 字节验证原样回显(bash /dev/tcp 零依赖),… #101

e2e 补 TCP echo:ecm/rndis 连设备侧 5000 端口发 5 字节验证原样回显(bash /dev/tcp 零依赖),…

e2e 补 TCP echo:ecm/rndis 连设备侧 5000 端口发 5 字节验证原样回显(bash /dev/tcp 零依赖),… #101

# 多平台 CMake 编译 + 测试
# 触发:push/PR 到 main,也可手动 workflow_dispatch
# 矩阵:Linux(gcc) 8 种 × macOS(clang) 8 种 × Windows(msvc) 4 种选项排列
name: CMake on multiple platforms
on:
push:
branches: [ "main" ]
# 文档等非代码文件的改动不触发编译
paths-ignore:
- '**/*.md'
- 'LICENSE'
- '.gitignore'
- '.github/**'
pull_request:
branches: [ "main" ]
paths-ignore:
- '**/*.md'
- 'LICENSE'
- '.gitignore'
- '.github/**'
workflow_dispatch:
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
# ======== Linux (gcc) : VIRTUAL_DEVICE × LIBUSB × SHARED 全排列 (8) ========
- { name: linux-gcc vdev+libusb+shared, os: ubuntu-24.04, cpp: g++, vdev: ON, libusb: ON, shared: ON }
- { name: linux-gcc vdev+libusb static, os: ubuntu-24.04, cpp: g++, vdev: ON, libusb: ON, shared: OFF }
- { name: linux-gcc vdev shared, os: ubuntu-24.04, cpp: g++, vdev: ON, libusb: OFF, shared: ON }
- { name: linux-gcc vdev static, os: ubuntu-24.04, cpp: g++, vdev: ON, libusb: OFF, shared: OFF }
- { name: linux-gcc libusb+shared, os: ubuntu-24.04, cpp: g++, vdev: OFF, libusb: ON, shared: ON }
- { name: linux-gcc libusb static, os: ubuntu-24.04, cpp: g++, vdev: OFF, libusb: ON, shared: OFF }
- { name: linux-gcc shared only, os: ubuntu-24.04, cpp: g++, vdev: OFF, libusb: OFF, shared: ON }
- { name: linux-gcc static only, os: ubuntu-24.04, cpp: g++, vdev: OFF, libusb: OFF, shared: OFF }
# ======== macOS (clang) : VIRTUAL_DEVICE × LIBUSB × SHARED 全排列 (8) ========
- { name: macos-clang vdev+libusb+shared, os: macos-latest, cpp: clang++, vdev: ON, libusb: ON, shared: ON }
- { name: macos-clang vdev+libusb static, os: macos-latest, cpp: clang++, vdev: ON, libusb: ON, shared: OFF }
- { name: macos-clang vdev shared, os: macos-latest, cpp: clang++, vdev: ON, libusb: OFF, shared: ON }
- { name: macos-clang vdev static, os: macos-latest, cpp: clang++, vdev: ON, libusb: OFF, shared: OFF }
- { name: macos-clang libusb+shared, os: macos-latest, cpp: clang++, vdev: OFF, libusb: ON, shared: ON }
- { name: macos-clang libusb static, os: macos-latest, cpp: clang++, vdev: OFF, libusb: ON, shared: OFF }
- { name: macos-clang shared only, os: macos-latest, cpp: clang++, vdev: OFF, libusb: OFF, shared: ON }
- { name: macos-clang static only, os: macos-latest, cpp: clang++, vdev: OFF, libusb: OFF, shared: OFF }
# ======== Windows (msvc) : VIRTUAL_DEVICE × SHARED (libusb 不开,runner 无 libusb) (4) ========
- { name: win-msvc vdev+shared, os: windows-latest, cpp: cl, vdev: ON, libusb: OFF, shared: ON }
- { name: win-msvc vdev static, os: windows-latest, cpp: cl, vdev: ON, libusb: OFF, shared: OFF }
- { name: win-msvc shared only, os: windows-latest, cpp: cl, vdev: OFF, libusb: OFF, shared: ON }
- { name: win-msvc static only, os: windows-latest, cpp: cl, vdev: OFF, libusb: OFF, shared: OFF }
steps:
- uses: actions/checkout@v4
# ======== 依赖 ========
- name: Install dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libasio-dev libspdlog-dev libgtest-dev libcxxopts-dev libevdev-dev libminiaudio-dev
if [ "${{ matrix.libusb }}" = "ON" ]; then
sudo apt-get install -y libusb-1.0-0-dev
fi
- name: Install dependencies (Windows)
if: runner.os == 'Windows'
run: |
vcpkg install asio spdlog gtest cxxopts miniaudio --triplet x64-windows
- name: Install dependencies (macOS)
if: runner.os == 'macOS'
run: |
brew install asio spdlog libusb cxxopts googletest miniaudio
# ======== CMake 配置 ========
- name: Configure CMake (Linux)
if: runner.os == 'Linux'
run: |
cmake -B build \
-DCMAKE_CXX_COMPILER=${{ matrix.cpp }} \
-DCMAKE_BUILD_TYPE=Release \
-DUSBIPDCPP_USE_PKGCONF_ASIO=ON \
-DUSBIPDCPP_BUILD_EXAMPLES=ON \
-DUSBIPDCPP_BUILD_TESTS=ON \
-DUSBIPDCPP_BUILD_VIRTUAL_DEVICE=${{ matrix.vdev }} \
-DUSBIPDCPP_BUILD_LIBUSB_COMPONENTS=${{ matrix.libusb }} \
-DUSBIPDCPP_BUILD_SHARED_LIBS=${{ matrix.shared }} \
-S ${{ github.workspace }}
- name: Configure CMake (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
cmake -B build `
-DCMAKE_CXX_COMPILER=${{ matrix.cpp }} `
-DCMAKE_BUILD_TYPE=Release `
-DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" `
-DUSBIPDCPP_BUILD_EXAMPLES=ON `
-DUSBIPDCPP_BUILD_TESTS=ON `
-DUSBIPDCPP_BUILD_VIRTUAL_DEVICE=${{ matrix.vdev }} `
-DUSBIPDCPP_BUILD_LIBUSB_COMPONENTS=${{ matrix.libusb }} `
-DUSBIPDCPP_BUILD_SHARED_LIBS=${{ matrix.shared }} `
-S ${{ github.workspace }}
- name: Configure CMake (macOS)
if: runner.os == 'macOS'
run: |
cmake -B build \
-DCMAKE_CXX_COMPILER=${{ matrix.cpp }} \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="$(brew --prefix)" \
-DUSBIPDCPP_USE_PKGCONF_ASIO=ON \
-DUSBIPDCPP_BUILD_EXAMPLES=ON \
-DUSBIPDCPP_BUILD_TESTS=ON \
-DUSBIPDCPP_BUILD_VIRTUAL_DEVICE=${{ matrix.vdev }} \
-DUSBIPDCPP_BUILD_LIBUSB_COMPONENTS=${{ matrix.libusb }} \
-DUSBIPDCPP_BUILD_SHARED_LIBS=${{ matrix.shared }} \
-S ${{ github.workspace }}
# ======== 编译 + 测试 ========
- name: Build
run: cmake --build build --config Release --parallel
- name: Test
working-directory: build
run: ctest --build-config Release --output-on-failure
# ======== 高频稳定性测试 ========
# 线程竞争、停止流程等偶发 bug 单次运行无法暴露(本地曾出现第 1303 次
# 循环才挂起的竞态),因此对曾出过 bug 的生命周期路径重复跑 50 轮直到失败。
# TestNetworkConnection 是连接/停止/竞态类测试(独立文件,与纯数据结构
# 测试分开),其中 StopDuringClientConnectRace 是唯一在 stop 时刻有连接
# 涌入的测试,直接覆盖 acceptor close 与 accept 的竞态(本次 CI 崩溃的
# 根因),虽在 Windows 上单次约 2 秒(线程/socket 开销),但保留。
# 注意必须 shell: bash:行尾 \ 续行是 bash 语法,Windows runner 默认
# pwsh 会把它当字面参数,导致 -R 过滤丢失、全部测试重复 50 轮(曾
# 造成 Windows job 跑 50 分钟)
- name: Stress test (TestNetworkConnection)
shell: bash
working-directory: build
run: |
# tee 保持实时日志输出;失败时在 runner 上就地符号化崩溃栈
# (二进制还在,tools/addr2line_crash.sh 解析日志里的偏移),
# 否则地址离开 runner 后永远无法解析
set +e
ctest --build-config Release --repeat until-fail:50 --timeout 120 --output-on-failure \
-R "TestNetworkConnection\.(ServerCanRestartAfterStop|ManyQuickConnections|StopWithMultipleClients|StopDuringClientConnectRace)" \
2>&1 | tee /tmp/stress.log
status=${PIPESTATUS[0]}
set -e
if [ $status -ne 0 ]; then
echo "=== 符号化崩溃栈 ==="
bash ${{ github.workspace }}/tools/addr2line_crash.sh /tmp/stress.log || true
exit $status
fi
- name: Stress test (TestNetworkVdev)
if: matrix.vdev == 'ON'
shell: bash
working-directory: build
run: |
set +e
ctest --build-config Release --repeat until-fail:50 --timeout 120 --output-on-failure \
-R "TestNetworkVdev\.(ServerCanStopWithImportedDevice|ClientDisconnectsDuringUrbTransfer)" \
2>&1 | tee /tmp/stress_vdev.log
status=${PIPESTATUS[0]}
set -e
if [ $status -ne 0 ]; then
echo "=== 符号化崩溃栈 ==="
bash ${{ github.workspace }}/tools/addr2line_crash.sh /tmp/stress_vdev.log || true
exit $status
fi
# Termux (Android) 编译验证。
# Termux rootfs 无 glibc,actions 的 node 程序(含 actions/checkout)无法在容器内运行,
# 因此 checkout 在 host 执行,构建挂载进 termux-docker 镜像进行。
# 镜像 entrypoint 自动从 root 切到容器用户(1000)(pkg 拒绝 root);
# workspace 挂载属主是 host 的 runner(1001),容器用户不可写,
# 构建目录放容器 HOME,workspace 仅作只读源码。
# cxxopts 不在 Termux 仓库,按 README 指南手动安装(其测试依赖 Android 系统库
# liblog 而镜像无 Android 运行时,需禁用);miniaudio 也没有,
# mock_audio 的 --audio 选项(AudioFileSource)自动禁用。
termux-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and test in Termux container
run: |
cat > /tmp/termux_ci.sh <<'EOF'
#!/bin/bash
set -e
pkg update -y
pkg install -y bash git clang cmake ninja pkg-config libasio libspdlog googletest libusb
git clone --depth 1 https://github.com/jarro2783/cxxopts.git ~/cxxopts
cd ~/cxxopts
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DCXXOPTS_BUILD_TESTS=OFF -DCXXOPTS_BUILD_EXAMPLES=OFF \
-DCMAKE_INSTALL_PREFIX="$PREFIX/opt/cxxopts"
cmake --build build
cmake --install build
cd /src
cmake -B ~/build-usbipdcpp -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DUSBIPDCPP_USE_PKGCONF_ASIO=ON \
-Dcxxopts_DIR="$PREFIX/opt/cxxopts/share/cmake/cxxopts" \
-DUSBIPDCPP_BUILD_EXAMPLES=ON \
-DUSBIPDCPP_BUILD_TESTS=ON \
-DUSBIPDCPP_BUILD_VIRTUAL_DEVICE=ON \
-DUSBIPDCPP_BUILD_LIBUSB_COMPONENTS=ON
cmake --build ~/build-usbipdcpp
cd ~/build-usbipdcpp && ctest
EOF
docker run --rm \
-v "$GITHUB_WORKSPACE:/src" \
-v /tmp/termux_ci.sh:/ci.sh \
termux/termux-docker:latest \
bash /ci.sh