hypertor 0.2.2

Tor HTTP client and onion service library with Python bindings
Documentation
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]
  schedule:
    # Run daily at 00:00 UTC to catch regressions and security issues
    - cron: '0 0 * * *'

env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: 1
  # Security: Minimize information leakage in CI logs
  CARGO_INCREMENTAL: 0

# Cancel in-progress runs for the same branch
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  # ============================================================================
  # Code Quality
  # ============================================================================
  fmt:
    name: Format Check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: rustfmt
      - run: cargo fmt --all -- --check

  clippy:
    name: Clippy Lints
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy
      - uses: Swatinem/rust-cache@v2
      # Strict warnings for library code (exclude python feature - tested separately)
      - name: Clippy (library)
        run: cargo clippy --lib --features "client,server,http2,padding,rustls" -- -D warnings
      # Allow unwrap() in tests/examples/benches
      - name: Clippy (tests & examples)
        run: cargo clippy --tests --examples --benches --features "client,server,http2,padding,rustls" -- -W clippy::unwrap_used

  # ============================================================================
  # Unit Tests (all platforms)
  # ============================================================================
  test:
    name: Test (${{ matrix.os }}, ${{ matrix.rust }})
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        rust: [stable, beta]
        include:
          - os: ubuntu-latest
            rust: nightly
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: ${{ matrix.rust }}
      - uses: Swatinem/rust-cache@v2
      
      # Run tests with rustls (default) - works on all platforms without system deps
      # Windows needs static-sqlite since SQLite isn't available as a system library
      - name: Run tests (rustls)
        if: matrix.os != 'windows-latest'
        run: cargo test --features "client,server,http2,padding,rustls" --workspace
      
      - name: Run tests (rustls + static-sqlite)
        if: matrix.os == 'windows-latest'
        run: cargo test --features "client,server,http2,padding,rustls,static-sqlite" --workspace
      
      # On Linux/macOS, also test native-tls (requires OpenSSL)
      - name: Run tests (native-tls)
        if: matrix.os != 'windows-latest'
        run: cargo test --no-default-features --features "client,server,http2,padding,native-tls" --workspace

  # ============================================================================
  # Feature Matrix Tests
  # ============================================================================
  features:
    name: Feature Combinations
    runs-on: ubuntu-latest
    strategy:
      matrix:
        features:
          # Default features (includes rustls + client)
          - ""
          # Core features with default TLS (rustls)
          - "client"
          - "server"
          - "http2"
          - "padding"
          # TLS backends (each enables a valid TLS stack)
          - "rustls"
          - "native-tls"
          # Combinations with explicit TLS
          - "client,server,http2,rustls"
          - "client,server,http2,native-tls"
          - "full"
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Check features (${{ matrix.features || 'default' }})
        run: cargo check --features "${{ matrix.features }}"

  # ============================================================================
  # Documentation
  # ============================================================================
  docs:
    name: Documentation
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@nightly
      - uses: Swatinem/rust-cache@v2
      - name: Build docs
        run: RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --features "client,server,http2,padding,rustls" --no-deps
        env:
          RUSTDOCFLAGS: --cfg docsrs -D warnings

  # ============================================================================
  # Security Audit
  # ============================================================================
  audit:
    name: Security Audit
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - name: Generate Cargo.lock
        run: cargo generate-lockfile
      - name: Install cargo-audit
        run: cargo install cargo-audit --locked
      - name: Run security audit
        run: cargo audit --ignore RUSTSEC-2023-0071

  # ============================================================================
  # Semver Compatibility Check
  # ============================================================================
  semver:
    name: Semver Check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Install cargo-semver-checks
        uses: taiki-e/install-action@cargo-semver-checks
      - name: Check semver compatibility
        run: cargo semver-checks check-release --default-features
        continue-on-error: true  # Advisory for now

  # ============================================================================
  # Dependency Ban & Advisory Check (license check removed - too volatile)
  # ============================================================================
  deny:
    name: Cargo Deny
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: EmbarkStudios/cargo-deny-action@v2
        with:
          command: check bans advisories sources

  # ============================================================================
  # Code Coverage
  # ============================================================================
  coverage:
    name: Code Coverage
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Install cargo-llvm-cov
        uses: taiki-e/install-action@cargo-llvm-cov
      - name: Generate coverage
        run: cargo llvm-cov --features "client,server,http2,padding,rustls" --workspace --lcov --output-path lcov.info
      - name: Upload to Codecov
        uses: codecov/codecov-action@v4
        with:
          files: lcov.info
          fail_ci_if_error: false

  # ============================================================================
  # MSRV Check
  # ============================================================================
  msrv:
    name: Minimum Supported Rust Version (1.86)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@1.86
      - uses: Swatinem/rust-cache@v2
      - run: cargo check --features "client,server,http2,padding,rustls"

  # ============================================================================
  # Python Bindings
  # ============================================================================
  python:
    name: Python Bindings (${{ matrix.python }})
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python: ['3.9', '3.10', '3.11', '3.12', '3.13']
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python }}
      - uses: Swatinem/rust-cache@v2
      
      - name: Install maturin and pytest
        run: pip install maturin pytest
      
      - name: Build wheel
        run: maturin build --features python --release
      
      - name: Install wheel
        run: pip install target/wheels/*.whl
      
      - name: Run Python tests
        run: pytest python/tests/ -v --tb=short
      
      - name: Test Python import
        run: |
          python -c "
          import hypertor
          print(f'hypertor version: {hypertor.__version__}')
          from hypertor import Client, AsyncClient, OnionApp, AppConfig
          print('All imports successful')
          "

  # ============================================================================
  # Security Tests
  # ============================================================================
  security-tests:
    name: Security Test Suite
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Run security tests
        run: cargo test --test security -- --nocapture
      - name: Verify no unsafe code
        run: |
          # Search for 'unsafe' keyword in Rust files, excluding legitimate uses
          # Uses find + grep instead of grep -r for consistent behavior across platforms
          UNSAFE_FOUND=$(find src -name "*.rs" -exec grep -l "unsafe" {} \; | xargs grep -n "unsafe" 2>/dev/null | \
            grep -v "forbid(unsafe_code)" | \
            grep -v "// SAFETY:" | \
            grep -v "#!\[" | \
            grep -v "#\[" || true)
          if [ -n "$UNSAFE_FOUND" ]; then
            echo "Found unexpected unsafe code:"
            echo "$UNSAFE_FOUND"
            exit 1
          fi
          echo "✓ No unexpected unsafe code found"

  # ============================================================================
  # Benchmarks (only on main)
  # ============================================================================
  benchmark:
    name: Performance Benchmarks
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Run benchmarks
        run: cargo bench --features "client,server,http2,padding,rustls" --bench benchmarks -- --noplot
      - name: Store benchmark result
        uses: benchmark-action/github-action-benchmark@v1
        with:
          tool: 'cargo'
          output-file-path: target/criterion/report/index.html
          fail-on-alert: false
        continue-on-error: true

  # ============================================================================
  # Release Check
  # ============================================================================
  release-check:
    name: Release Readiness
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Check package
        run: cargo package --allow-dirty
      - name: Check publish (dry-run)
        run: cargo publish --dry-run --allow-dirty