name: tests on: push: branches: [master] pull_request: branches: [master] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: actions/setup-go@v6 with: go-version: '1.25' cache: false - uses: actions/setup-python@v7 with: python-version: '3.12' - name: Install pinned linters run: | go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 python -m pip install ruff==0.15.6 - name: Lint GitHub Actions workflows run: actionlint -shellcheck= -pyflakes= - name: Lint Python scripts and tests run: ruff check --select E9,F63,F7,F82 tests scripts tests: strategy: fail-fast: false matrix: os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - name: Install dependencies (Linux) if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install -y libpcre2-dev libmbedtls-dev libsodium-dev libuv1-dev libc-ares-dev valgrind - name: Install dependencies (macOS) if: runner.os == 'macOS' run: brew install mbedtls@3 libsodium libuv c-ares pcre2 - name: Build test binaries run: | mkdir -p build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release jobs="$(nproc 2>/dev/null || sysctl -n hw.ncpu)" make -j"$jobs" - name: CTest run: ctest --test-dir build -LE memcheck --output-on-failure --no-tests=error - name: Valgrind leak smoke tests if: runner.os == 'Linux' run: ctest --test-dir build -L memcheck --output-on-failure --no-tests=error - name: ss-setup TUI tests run: bash tests/test_ss_setup.sh - name: Stress test run: | python3 tests/stress_test.py --bin build/bin/ --size 10 - name: ss-redir transparent proxy test (QEMU) if: runner.os == 'Linux' run: | sudo apt-get install -y qemu-system-x86 bash tests/test_redir_qemu.sh build/bin/ timeout-minutes: 8 asan: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y libpcre2-dev libmbedtls-dev libsodium-dev libuv1-dev libc-ares-dev - name: Build with ASan + UBSan run: | mkdir -p build && cd build cmake .. -DENABLE_SANITIZERS=ON make -j"$(nproc)" - name: CTest (ASan + UBSan) run: ctest --test-dir build -LE memcheck --output-on-failure --no-tests=error - name: Stress test (ASan + UBSan) run: python3 tests/stress_test.py --bin build/bin/ --size 10 clang-tidy: runs-on: ubuntu-latest env: # Ratchet: number of known clang-tidy warnings (see .clang-tidy), # counted with clang-tidy-18 (pinned below so runner upgrades don't move # the number). All baseline findings have been fixed or triaged # (false positives carry NOLINT comments with rationale), so any new # warning fails CI. MAX_WARNINGS: 0 steps: - uses: actions/checkout@v6 - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y clang-tidy-18 clang-tools-18 libpcre2-dev libmbedtls-dev libsodium-dev libuv1-dev libc-ares-dev - name: Configure (compile_commands.json) run: cmake -S . -B build - name: Run clang-tidy on project sources run: | set -o pipefail tidy_status=0 run-clang-tidy-18 -quiet -p build "$PWD/src/[^/]+\.c$" 2>&1 | tee tidy.log || tidy_status=$? grep 'warning:' tidy.log | sort -u > warnings.txt || true count="$(wc -l < warnings.txt)" echo "clang-tidy warnings: $count (max allowed: $MAX_WARNINGS)" { echo "### clang-tidy: $count warnings (budget: $MAX_WARNINGS)" echo '```' cat warnings.txt echo '```' } >> "$GITHUB_STEP_SUMMARY" if [ "$tidy_status" -ne 0 ] || grep -qE 'error:' tidy.log; then echo "clang-tidy failed (exit status: $tidy_status). See tidy.log for diagnostics." exit 1 fi if [ "$count" -gt "$MAX_WARNINGS" ]; then echo "FAIL: warning count $count exceeds budget $MAX_WARNINGS." echo "Fix the new warnings (or, if a check is misfiring, adjust .clang-tidy)." exit 1 fi - name: Upload clang-tidy diagnostics if: always() uses: actions/upload-artifact@v4 with: name: clang-tidy-diagnostics path: | tidy.log warnings.txt if-no-files-found: ignore coverage: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Install dependencies run: | sudo apt-get update sudo apt-get install -y lcov libpcre2-dev libmbedtls-dev libsodium-dev libuv1-dev libc-ares-dev - name: Build with coverage instrumentation run: | mkdir -p build && cd build cmake .. -DENABLE_COVERAGE=ON make -j"$(nproc)" - name: Run tests run: | ctest --test-dir build -LE memcheck --output-on-failure --no-tests=error python3 tests/stress_test.py --bin build/bin/ --size 10 - name: Generate coverage report run: | cmake --build build --target coverage { echo '### Coverage (src/, unit + stress tests)' echo '```' lcov --list build/coverage.info --ignore-errors unused,empty echo '```' } >> "$GITHUB_STEP_SUMMARY" - name: Upload HTML report uses: actions/upload-artifact@v4 with: name: coverage-html path: build/coverage-html/ interop: runs-on: macos-latest steps: - uses: actions/checkout@v6 - name: Install dependencies and independent peer run: brew install mbedtls@3 libsodium libuv c-ares pcre2 shadowsocks-rust - name: Build run: | cmake -S . -B build -DSS_DEPENDENCY_MODE=system -DWITH_STATIC=OFF cmake --build build --parallel - name: Required interoperability env: SS_REQUIRE_INTEROP: '1' SS_BIN_DIR: build/bin run: bash tests/test_interop_rust.sh