CI #995
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: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: "0 * * * *" | |
| permissions: | |
| contents: read | |
| actions: read | |
| jobs: | |
| preflight: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| run_ci: ${{ steps.gate.outputs.run_ci }} | |
| steps: | |
| - name: Decide whether to run full CI | |
| id: gate | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const eventName = context.eventName; | |
| if (eventName === "workflow_dispatch") { | |
| core.notice("Manual dispatch: running full CI."); | |
| core.setOutput("run_ci", "true"); | |
| return; | |
| } | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const workflowId = ".github/workflows/ci.yml"; | |
| const now = Date.now(); | |
| const oneDayMs = 24 * 60 * 60 * 1000; | |
| const currentRunId = Number(process.env.GITHUB_RUN_ID); | |
| const { data } = await github.rest.actions.listWorkflowRuns({ | |
| owner, | |
| repo, | |
| workflow_id: workflowId, | |
| event: "schedule", | |
| per_page: 100, | |
| status: "completed", | |
| }); | |
| const recent = data.workflow_runs.find((run) => { | |
| if (run.id === currentRunId) { | |
| return false; | |
| } | |
| if (run.conclusion !== "success") { | |
| return false; | |
| } | |
| const started = Date.parse(run.run_started_at || run.created_at); | |
| return Number.isFinite(started) && (now - started) < oneDayMs; | |
| }); | |
| if (recent) { | |
| core.notice(`Skipping full CI: successful scheduled run already happened at ${recent.run_started_at || recent.created_at}.`); | |
| core.setOutput("run_ci", "false"); | |
| } else { | |
| core.notice("No successful scheduled run in last 24h: running full CI."); | |
| core.setOutput("run_ci", "true"); | |
| } | |
| skip-due-to-daily-limit: | |
| needs: preflight | |
| if: needs.preflight.outputs.run_ci != 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Skip heavy jobs | |
| run: echo "CI already ran successfully in the last 24h. Exiting early." | |
| lint-type: | |
| needs: preflight | |
| if: needs.preflight.outputs.run_ci == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install uv | |
| run: python -m pip install uv | |
| - name: Install dependencies | |
| run: | | |
| python bootstrap.py --with-test --python-version "3.12" | |
| - name: Ruff | |
| run: .venv/bin/python -m ruff check implementation/python/voxlogica tests | |
| - name: MyPy | |
| run: .venv/bin/python -m mypy implementation/python/voxlogica --ignore-missing-imports | |
| - name: Docs link check | |
| run: | | |
| .venv/bin/python - <<'PY' | |
| from pathlib import Path | |
| import re | |
| import sys | |
| pattern = re.compile(r"\[[^\]]+\]\(([^)]+)\)") | |
| failures = [] | |
| for doc in Path("doc/dev").glob("*.md"): | |
| text = doc.read_text(encoding="utf-8") | |
| for match in pattern.finditer(text): | |
| target = match.group(1).strip() | |
| if target.startswith(("http://", "https://", "#", "mailto:")): | |
| continue | |
| resolved = (doc.parent / target).resolve() | |
| if not resolved.exists(): | |
| failures.append(f"{doc}: missing link target '{target}'") | |
| if failures: | |
| for item in failures: | |
| print(item) | |
| sys.exit(1) | |
| print("Doc links OK") | |
| PY | |
| test-unit-contract: | |
| needs: preflight | |
| if: needs.preflight.outputs.run_ci == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.11", "3.12", "3.13"] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Install uv | |
| run: python -m pip install uv | |
| - name: Install dependencies | |
| run: | | |
| python bootstrap.py --with-test --python-version "${{ matrix.python-version }}" | |
| - name: Run unit + contract | |
| run: .venv/bin/python -m pytest -m "unit or contract" | |
| test-integration: | |
| needs: preflight | |
| if: needs.preflight.outputs.run_ci == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install uv | |
| run: python -m pip install uv | |
| - name: Install dependencies | |
| run: | | |
| python bootstrap.py --with-test --python-version "3.12" | |
| - name: Fetch VoxLogicA-1 binary (best-effort) | |
| run: .venv/bin/python tests/fetch_vox1_binary.py --quiet || true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Run integration + regression | |
| run: .venv/bin/python -m pytest -m "integration or regression" | |
| test-compat-cli: | |
| needs: preflight | |
| if: needs.preflight.outputs.run_ci == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install uv | |
| run: python -m pip install uv | |
| - name: Install dependencies | |
| run: | | |
| python bootstrap.py --with-test --python-version "3.12" | |
| - name: CLI smoke | |
| run: | | |
| .venv/bin/python -m voxlogica.main version | |
| .venv/bin/python -m voxlogica.main run tests/closure_basic_test.imgql --no-execute | |
| env: | |
| PYTHONPATH: implementation/python | |
| coverage: | |
| needs: preflight | |
| if: needs.preflight.outputs.run_ci == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install uv | |
| run: python -m pip install uv | |
| - name: Install dependencies | |
| run: | | |
| python bootstrap.py --with-test --python-version "3.12" | |
| - name: Fetch VoxLogicA-1 binary (best-effort) | |
| run: .venv/bin/python tests/fetch_vox1_binary.py --quiet || true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Run full coverage suite | |
| run: .venv/bin/python -m pytest -m "unit or contract or integration or regression" --cov=implementation/python/voxlogica --cov-report=xml --cov-report=term-missing --cov-fail-under=60 | |
| - name: Upload coverage artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-report | |
| path: coverage.xml | |
| perf-smoke: | |
| needs: preflight | |
| if: github.event_name == 'schedule' && needs.preflight.outputs.run_ci == 'true' | |
| continue-on-error: true | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.12" | |
| - name: Install uv | |
| run: python -m pip install uv | |
| - name: Install dependencies | |
| run: | | |
| python bootstrap.py --with-test --python-version "3.12" | |
| - name: Fetch VoxLogicA-1 binary (best-effort) | |
| run: .venv/bin/python tests/fetch_vox1_binary.py --quiet || true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Run perf smoke | |
| run: .venv/bin/python -m pytest -m perf --maxfail=1 |