test: fix async coroutine warnings and CI compatibility #26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main, classic-next, develop] | |
| pull_request: | |
| branches: [main, classic-next, develop] | |
| env: | |
| RUST_BACKTRACE: 1 | |
| CARGO_TERM_COLOR: always | |
| # Python version must match project requirements | |
| PYTHON_VERSION: "3.12" | |
| jobs: | |
| # Formatting checks (non-blocking, aesthetic only) | |
| format-python: | |
| name: Python Formatting (Ruff) | |
| runs-on: windows-latest | |
| timeout-minutes: 10 | |
| continue-on-error: true # Don't block CI on formatting issues | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v4 | |
| - name: Set up Python | |
| run: uv python install ${{ env.PYTHON_VERSION }} | |
| - name: Install dependencies | |
| run: uv sync --all-extras | |
| - name: Run ruff format check | |
| run: uv run ruff format --check . | |
| format-rust: | |
| name: Rust Formatting (rustfmt) | |
| runs-on: windows-latest | |
| timeout-minutes: 10 | |
| continue-on-error: true # Don't block CI on formatting issues | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: rustfmt | |
| - name: Run rustfmt check | |
| run: cargo fmt --all --manifest-path rust/Cargo.toml -- --check | |
| # Linting checks (can catch real bugs, run in parallel with builds) | |
| lint-python: | |
| name: Python Linting (Ruff) | |
| runs-on: windows-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v4 | |
| - name: Set up Python | |
| run: uv python install ${{ env.PYTHON_VERSION }} | |
| - name: Install dependencies | |
| run: uv sync --all-extras | |
| - name: Run ruff check | |
| run: uv run ruff check . | |
| lint-rust: | |
| name: Rust Linting (Clippy) | |
| runs-on: windows-latest | |
| timeout-minutes: 60 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: clippy | |
| - name: Cache cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cargo/registry | |
| key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-registry- | |
| - name: Cache cargo index | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cargo/git | |
| key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-index- | |
| - name: Cache cargo build | |
| uses: actions/cache@v4 | |
| with: | |
| path: rust/target | |
| key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('rust/**/*.rs') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }}- | |
| ${{ runner.os }}-cargo-build- | |
| - name: Run clippy | |
| run: cargo clippy --workspace --all-targets --all-features --manifest-path rust/Cargo.toml -- -D warnings | |
| # Build Rust components (no dependencies - runs immediately) | |
| build-rust: | |
| name: Build Rust Components | |
| runs-on: windows-latest | |
| timeout-minutes: 60 # Prevent build from hanging | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cargo/registry | |
| key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-registry- | |
| - name: Cache cargo index | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cargo/git | |
| key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-index- | |
| - name: Cache cargo build | |
| uses: actions/cache@v4 | |
| with: | |
| path: rust/target | |
| key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('rust/**/*.rs') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }}- | |
| ${{ runner.os }}-cargo-build- | |
| - name: Build all Rust workspace | |
| run: cargo build --workspace --release --manifest-path rust/Cargo.toml | |
| - name: Run Rust tests | |
| run: | | |
| # Run most tests in parallel | |
| cargo test --workspace --release --manifest-path rust/Cargo.toml --exclude classic-gui-slint | |
| # Run Slint GUI tests single-threaded to avoid Windows heap corruption during cleanup | |
| # (all tests pass; crash only occurs during multi-threaded process shutdown) | |
| cargo test --release --manifest-path rust/Cargo.toml -p classic-gui-slint -- --test-threads=1 | |
| - name: Upload Rust build artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: rust-libs | |
| path: | | |
| rust/target/release/*.dll | |
| rust/target/release/*.pdb | |
| retention-days: 1 | |
| # Build Python bindings with maturin | |
| build-python-bindings: | |
| name: Build Python Bindings | |
| runs-on: windows-latest | |
| needs: [build-rust] # Only wait for Rust build, not linting | |
| timeout-minutes: 30 # Maturin builds can be slow | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Cache cargo registry | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cargo/registry | |
| key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-registry- | |
| - name: Cache cargo build | |
| uses: actions/cache@v4 | |
| with: | |
| path: rust/target | |
| key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('rust/**/*.rs') }} | |
| restore-keys: | | |
| ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }}- | |
| ${{ runner.os }}-cargo-build- | |
| - name: Install maturin | |
| run: uv tool install maturin | |
| - name: Build all Python bindings | |
| run: | | |
| $ErrorActionPreference = "Stop" | |
| # Map binding names to their directory paths | |
| # Most are in python-bindings/, but classic-shared-py is in foundation/ | |
| $bindingPaths = @{ | |
| "classic-shared-py" = "rust/foundation/classic-shared-py" | |
| "classic-yaml-py" = "rust/python-bindings/classic-yaml-py" | |
| "classic-database-py" = "rust/python-bindings/classic-database-py" | |
| "classic-file-io-py" = "rust/python-bindings/classic-file-io-py" | |
| "classic-scanlog-py" = "rust/python-bindings/classic-scanlog-py" | |
| "classic-config-py" = "rust/python-bindings/classic-config-py" | |
| "classic-registry-py" = "rust/python-bindings/classic-registry-py" | |
| "classic-perf-py" = "rust/python-bindings/classic-perf-py" | |
| "classic-pybridge-py" = "rust/python-bindings/classic-pybridge-py" | |
| "classic-settings-py" = "rust/python-bindings/classic-settings-py" | |
| "classic-message-py" = "rust/python-bindings/classic-message-py" | |
| "classic-path-py" = "rust/python-bindings/classic-path-py" | |
| "classic-constants-py" = "rust/python-bindings/classic-constants-py" | |
| "classic-version-py" = "rust/python-bindings/classic-version-py" | |
| "classic-resource-py" = "rust/python-bindings/classic-resource-py" | |
| "classic-xse-py" = "rust/python-bindings/classic-xse-py" | |
| "classic-web-py" = "rust/python-bindings/classic-web-py" | |
| "classic-scangame-py" = "rust/python-bindings/classic-scangame-py" | |
| "classic-update-py" = "rust/python-bindings/classic-update-py" | |
| } | |
| # Build in dependency order (classic-shared-py first) | |
| $buildOrder = @( | |
| "classic-shared-py", | |
| "classic-yaml-py", | |
| "classic-database-py", | |
| "classic-file-io-py", | |
| "classic-scanlog-py", | |
| "classic-config-py", | |
| "classic-registry-py", | |
| "classic-perf-py", | |
| "classic-pybridge-py", | |
| "classic-settings-py", | |
| "classic-message-py", | |
| "classic-path-py", | |
| "classic-constants-py", | |
| "classic-version-py", | |
| "classic-resource-py", | |
| "classic-xse-py", | |
| "classic-web-py", | |
| "classic-scangame-py", | |
| "classic-update-py" | |
| ) | |
| foreach ($binding in $buildOrder) { | |
| $path = $bindingPaths[$binding] | |
| Write-Host "Building $binding from $path..." | |
| Set-Location $path | |
| maturin build --release --out dist | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "Failed to build $binding" | |
| } | |
| Set-Location $env:GITHUB_WORKSPACE | |
| } | |
| shell: pwsh | |
| - name: Upload Python binding wheels | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: python-wheels | |
| path: | | |
| rust/python-bindings/*/dist/*.whl | |
| rust/foundation/*/dist/*.whl | |
| retention-days: 1 | |
| # Run Python tests | |
| test-python: | |
| name: Python Tests | |
| runs-on: windows-latest | |
| needs: [build-python-bindings] | |
| timeout-minutes: 30 # Kill job if tests hang for 30 minutes | |
| env: | |
| PYTHONTRACEMALLOC: "10" # Enable tracemalloc with 10 frame depth for better error reporting | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v4 | |
| - name: Set up Python | |
| run: uv python install ${{ env.PYTHON_VERSION }} | |
| - name: Download Python binding wheels | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: python-wheels | |
| path: wheels | |
| - name: Install Python dependencies and wheels | |
| run: | | |
| uv sync --all-extras | |
| # Debug: Show workflow version | |
| Write-Host "CI workflow version: 2024-11-27-v6" | |
| # Install pip into the venv so we can use it directly | |
| uv pip install pip | |
| # List downloaded wheels for debugging | |
| Write-Host "Downloaded wheels:" | |
| $wheels = Get-ChildItem -Path wheels -Filter *.whl -Recurse | |
| $wheels | ForEach-Object { Write-Host " $($_.FullName)" } | |
| Write-Host "Total wheels found: $($wheels.Count)" | |
| # Install each wheel using pip directly (bypasses uv lockfile) | |
| foreach ($wheel in $wheels) { | |
| Write-Host "Installing: $($wheel.Name)" | |
| uv run python -m pip install $wheel.FullName --force-reinstall --no-deps | |
| } | |
| # Verify installation | |
| Write-Host "Installed packages (classic*):" | |
| uv run python -m pip list | Select-String "classic" | |
| shell: pwsh | |
| - name: Verify Rust acceleration | |
| run: | | |
| uv run python -c "import classic_yaml; print(f'Rust YAML version: {classic_yaml.__version__}')" | |
| uv run python -c "from ClassicLib.integration.status import print_rust_status; print_rust_status()" | |
| - name: Run pytest (unit tests) | |
| run: uv run pytest -m "unit and not slow" --maxfail=5 --tb=short --timeout=300 --timeout-method=thread | |
| timeout-minutes: 10 | |
| - name: Run pytest (integration tests) | |
| run: uv run pytest -m "integration" --maxfail=3 --tb=short --timeout=600 --timeout-method=thread | |
| timeout-minutes: 15 | |
| - name: Run pytest (Rust integration tests) | |
| run: uv run pytest tests/rust_integration/ -v --maxfail=3 --tb=short --timeout=300 --timeout-method=thread | |
| timeout-minutes: 10 | |
| - name: Upload test results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: pytest-results | |
| path: | | |
| .pytest_cache/ | |
| test-results/ | |
| retention-days: 7 |