docs: v0.3.2 GA 마무리 — Frozen 전환 #68
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 | |
| # 트리거: PR / main 푸시 — test 계열만 (wheel 빌드 + 배포는 publish.yml) | |
| # 문서·라이선스·gitignore 전용 변경은 changes job + job-level if 패턴으로 if-skip | |
| # (status: success). paths-ignore 안 씀 — required check "All tests passed" 가 | |
| # expected 로 stuck 되는 GitHub 함정 회피. | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| workflow_dispatch: {} | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| # * 코드 변경 감지 — docs / LICENSE / .gitignore 전용 변경이면 test 잡 if-skip | |
| # (status: success). 모든 변경 파일이 exclude 패턴에 매치돼야 code=false. | |
| # base SHA 부재 / zero SHA 면 안전 측면으로 code=true (= 전체 실행). | |
| changes: | |
| name: Detect code changes | |
| runs-on: ubuntu-latest | |
| outputs: | |
| code: ${{ steps.filter.outputs.code }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - id: filter | |
| env: | |
| BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }} | |
| run: | | |
| if [ -z "$BASE_SHA" ] || [ "$BASE_SHA" = "0000000000000000000000000000000000000000" ]; then | |
| echo "code=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # ^ three-dot diff = base..HEAD 공통 조상부터의 변경만 (PR diff 와 동일) | |
| if git diff --name-only "$BASE_SHA"...HEAD \ | |
| | grep -qvE '(^|/)([^/]+\.md|LICENSE[^/]*|\.gitignore)$|^docs/'; then | |
| echo "code=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "code=false" >> "$GITHUB_OUTPUT" | |
| fi | |
| # * Linux abi3 wheel 1회 빌드 → 모든 Linux 잡(test×4 / slow / core-only)이 공유 | |
| # abi3-py310 이라 py3.10/3.11/3.12/3.13 가 동일 wheel 재사용 가능. | |
| # macOS/Windows 는 단일 잡이라 빌드/테스트 분리 이득이 없어 그대로 매번 빌드. | |
| build-linux-wheel: | |
| name: Build Linux abi3 wheel | |
| needs: changes | |
| if: needs.changes.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| submodules: recursive | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - uses: astral-sh/setup-uv@v8.1.0 | |
| with: | |
| python-version: "3.12" | |
| - run: uv sync --no-install-project --group all | |
| - run: uv run maturin build --release --out dist | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: rhwp-python-linux-wheel | |
| path: dist/*.whl | |
| retention-days: 1 | |
| # * 메인 테스트 + 린트 + 타입체크 (Linux × 전 Python 버전 — wheel 공유) | |
| test: | |
| name: Test (Linux / py${{ matrix.python }}) | |
| needs: [build-linux-wheel, changes] | |
| if: needs.changes.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { python: "3.10", lint: true } | |
| - { python: "3.11" } | |
| - { python: "3.12" } | |
| - { python: "3.13" } | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| submodules: recursive | |
| - uses: astral-sh/setup-uv@v8.1.0 | |
| with: | |
| python-version: ${{ matrix.python }} | |
| - run: uv sync --no-install-project --group all | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: rhwp-python-linux-wheel | |
| path: dist/ | |
| - run: uv pip install --reinstall dist/*.whl | |
| - name: Run pytest (not slow) with coverage | |
| run: uv run pytest tests/ -m "not slow" --cov=rhwp --cov-report=term-missing -v | |
| - name: Run pyright (normal) | |
| if: matrix.lint | |
| run: | | |
| uv run pyright python/ \ | |
| tests/test_smoke.py tests/test_parse.py tests/test_text_extraction.py \ | |
| tests/test_errors.py tests/test_svg_rendering.py tests/test_pdf_rendering.py \ | |
| tests/test_langchain_loader.py tests/test_langchain_loader_ir.py \ | |
| tests/test_ir_schema.py tests/test_ir_roundtrip.py tests/test_ir_tables.py \ | |
| tests/test_ir_iter_blocks.py tests/test_ir_schema_export.py \ | |
| tests/test_ir_picture.py tests/test_ir_furniture.py \ | |
| tests/test_ir_formula.py tests/test_ir_footnote.py \ | |
| tests/test_ir_list.py tests/test_ir_caption.py \ | |
| tests/test_ir_toc.py tests/test_ir_field.py \ | |
| tests/test_v0_3_1_marker_char_offset.py \ | |
| tests/test_cli.py \ | |
| tests/conftest.py tests/type_check_samples.py | |
| - name: Run pyright (intentional errors — expect 4) | |
| if: matrix.lint | |
| run: | | |
| set +e | |
| uv run pyright --outputjson tests/type_check_errors.py > pyright-errors.json | |
| count=$(uv run python -c "import json; print(json.load(open('pyright-errors.json'))['summary']['errorCount'])") | |
| echo "intentional error count: $count" | |
| if [ "$count" != "4" ]; then | |
| echo "::error::expected 4 intentional errors, got $count" | |
| exit 1 | |
| fi | |
| # * macOS / Windows 스모크 — 단일 잡이라 wheel 분리 이득 없음 → 직접 maturin develop | |
| test-other-os: | |
| name: Test (${{ matrix.os }} / py3.12) | |
| needs: changes | |
| if: needs.changes.outputs.code == 'true' | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [macos-latest, windows-latest] | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| submodules: recursive | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - uses: astral-sh/setup-uv@v8.1.0 | |
| with: | |
| python-version: "3.12" | |
| - run: uv sync --no-install-project --group all | |
| - run: uv run maturin develop --release | |
| - run: uv run pytest tests/ -m "not slow" -v | |
| # * PDF 렌더링 — 느려서 별도 잡, Linux wheel 재사용 | |
| test-slow: | |
| name: Test slow (Linux / py3.12 — PDF) | |
| needs: [build-linux-wheel, changes] | |
| if: needs.changes.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| submodules: recursive | |
| - uses: astral-sh/setup-uv@v8.1.0 | |
| with: | |
| python-version: "3.12" | |
| - run: uv sync --no-install-project --group testing | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: rhwp-python-linux-wheel | |
| path: dist/ | |
| - run: uv pip install --reinstall dist/*.whl | |
| - run: uv run pytest tests/ -m slow -v | |
| # * Rust unit tests — src/ir.rs 의 #[cfg(test)] 모듈 실행 | |
| # Cargo.toml 의 default features 에서 extension-module 이 빠져 있어 libpython 링크 시도 안 함. | |
| # utf16_to_cp / simple_eq_text_alt / field_type_to_str / caption_direction_to_str / | |
| # assert_position_invariant (#[should_panic]) 검증 — Python 측에서 우회 불가능한 Rust 도메인 로직. | |
| cargo-test: | |
| name: Cargo test (Rust unit tests) | |
| needs: changes | |
| if: needs.changes.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| submodules: recursive | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - name: Run cargo test (default features — extension-module disabled) | |
| run: cargo test --lib | |
| # * extras 미설치 시 langchain 테스트가 importorskip 로 auto-skip 되는지 검증 | |
| test-core-only: | |
| name: Test without extras (importorskip auto-skip) | |
| needs: [build-linux-wheel, changes] | |
| if: needs.changes.outputs.code == 'true' | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| submodules: recursive | |
| - uses: astral-sh/setup-uv@v8.1.0 | |
| with: | |
| python-version: "3.12" | |
| - name: Install pytest only (no langchain extras — intentional) | |
| run: | | |
| uv venv | |
| uv pip install pytest | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: rhwp-python-linux-wheel | |
| path: dist/ | |
| - run: uv pip install dist/*.whl | |
| - name: Run pytest — extras-gated tests must auto-skip via importorskip | |
| # ^ 파일-레벨 importorskip 은 해당 파일 전체를 skip 1개로 카운트. | |
| # v0.3.0 기준 gated 파일: test_langchain_loader.py + test_langchain_loader_ir.py | |
| # (langchain-core), test_ir_schema_export.py (jsonschema), test_cli.py (typer) | |
| # → 총 4 파일. test_async.py 는 v0.3.0 부터 stdlib 만 사용 (aiofiles 의존성 제거) | |
| run: | | |
| uv run pytest tests/ -m "not slow" -v | tee pytest-output.txt | |
| if ! grep -qE '(^|[^0-9])4 skipped([^0-9]|$)' pytest-output.txt; then | |
| echo "::error::expected 4 extras-gated files to auto-skip via importorskip (langchain×2, jsonschema, typer)" | |
| exit 1 | |
| fi | |
| all-tests-passed: | |
| name: All tests passed | |
| if: always() | |
| runs-on: ubuntu-latest | |
| needs: [changes, build-linux-wheel, test, test-other-os, test-slow, test-core-only, cargo-test] | |
| steps: | |
| - uses: re-actors/alls-green@release/v1 | |
| with: | |
| jobs: ${{ toJSON(needs) }} | |
| allowed-skips: build-linux-wheel, test, test-other-os, test-slow, test-core-only, cargo-test |