chore: run ruff only at the pre-commit stage #2900
Workflow file for this run
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: "public/tidy3d/python-client-tests" | |
on: | |
merge_group: | |
workflow_dispatch: | |
inputs: | |
remote_tests: | |
description: 'remote-tests' | |
type: boolean | |
default: true | |
local_tests: | |
description: 'local-tests' | |
type: boolean | |
default: false | |
pull_request: | |
branches: | |
- latest | |
- develop | |
- 'pre/*' | |
types: ['opened', 'reopened', 'synchronize', 'ready_for_review', 'edited'] | |
pull_request_review: | |
types: [submitted] | |
permissions: | |
contents: read | |
pull-requests: write | |
jobs: | |
determine-test-scope: | |
runs-on: ubuntu-latest | |
if: | | |
github.event.pull_request.draft == false || | |
github.ref == 'refs/heads/develop' || | |
github.event_name == 'workflow_dispatch' | |
outputs: | |
local_tests: ${{ steps.determine-test-type.outputs.local_tests }} | |
remote_tests: ${{ steps.determine-test-type.outputs.remote_tests }} | |
pr_approval_state: ${{ steps.approval.outputs.approved }} | |
steps: | |
- name: check-current-approval-status | |
id: approval | |
if: github.event_name == 'pull_request' | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const {owner, repo} = context.repo; | |
const number = context.payload.pull_request.number; | |
// Fetch all reviews across all pages | |
const allReviews = await github.paginate(github.rest.pulls.listReviews, { | |
owner, | |
repo, | |
pull_number: number, | |
per_page: 100, | |
}); | |
core.info(`Found ${allReviews.length} total review events.`); | |
// Process the array to get only the latest review per user | |
const latestByUser = {}; | |
allReviews.forEach(review => { | |
if (review.state !== 'COMMENTED') { | |
latestByUser[review.user.id] = review; | |
} | |
}); | |
const latestStates = Object.values(latestByUser).map(review => review.state); | |
core.info(`Final review states from unique reviewers: [${latestStates.join(', ')}]`); | |
// The rest of the logic remains the same | |
const isBlocked = latestStates.includes('CHANGES_REQUESTED'); | |
const isApproved = latestStates.includes('APPROVED'); | |
const finalStatus = isApproved && !isBlocked; | |
core.info(`🏁 Final determined approval status is: ${finalStatus}`); | |
core.setOutput('approved', finalStatus ? 'true' : 'false'); | |
- name: determine-test-type | |
id: determine-test-type | |
env: | |
DRAFT_STATE: ${{ github.event.pull_request.draft }} | |
EVENT_NAME: ${{ github.event_name }} | |
REVIEW_STATE: ${{ github.event.review.state }} | |
REF: ${{ github.ref }} | |
INPUT_LOCAL: ${{ github.event.inputs.local_tests }} | |
INPUT_REMOTE: ${{ github.event.inputs.remote_tests }} | |
APPROVED: ${{ steps.approval.outputs.approved }} | |
run: | | |
echo "Event: $EVENT_NAME" | |
echo "Draft: $DRAFT_STATE" | |
echo "Review State: $REVIEW_STATE" | |
echo "Git REF: $REF" | |
echo "Input local: $INPUT_LOCAL" | |
echo "Input remote: $INPUT_REMOTE" | |
echo "Approved: $APPROVED" | |
remote_tests=false | |
local_tests=false | |
# Workflow_dispatch input override | |
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then | |
# Each option is self contained | |
if [[ "$INPUT_REMOTE" == "true" ]]; then | |
remote_tests=true | |
fi | |
if [[ "$INPUT_LOCAL" == "true" ]]; then | |
local_tests=true | |
fi | |
fi | |
# All PRs that have been triggered need local tests and approved ones need to re-run the remote tests | |
if [[ "$EVENT_NAME" == "pull_request" ]]; then | |
local_tests=true | |
[[ "$APPROVED" == "true" ]] && remote_tests=true | |
fi | |
if [[ "$EVENT_NAME" == "merge_group" ]]; then | |
local_tests=true | |
remote_tests=true | |
fi | |
# If triggered by PR review and approved | |
if [[ "$EVENT_NAME" == "pull_request_review" ]]; then | |
if [[ "$REVIEW_STATE" == "approved" ]]; then | |
local_tests=true | |
remote_tests=true | |
else | |
local_tests=false | |
remote_tests=false | |
fi | |
fi | |
# If it's a push to develop | |
if [[ "$EVENT_NAME" == "push" && "$REF" == "refs/heads/develop" ]]; then | |
local_tests=true | |
remote_tests=true | |
fi | |
echo "local_tests=$local_tests" >> $GITHUB_OUTPUT | |
echo "remote_tests=$remote_tests" >> $GITHUB_OUTPUT | |
echo "local_tests=$local_tests" | |
echo "remote_tests=$remote_tests" | |
lint: | |
needs: determine-test-scope | |
if: ( needs.determine-test-scope.outputs.local_tests == 'true' ) || ( needs.determine-test-scope.outputs.remote_tests == 'true' ) | |
name: verify-linting | |
runs-on: ubuntu-latest | |
container: ghcr.io/astral-sh/uv:debian | |
steps: | |
- uses: actions/checkout@v4 | |
with: | |
fetch-depth: 1 | |
submodules: false | |
- uses: astral-sh/ruff-action@v3 | |
with: | |
version: 0.11.11 | |
- name: Run ruff format | |
run: ruff format --check --diff | |
- name: Run ruff check | |
run: ruff check tidy3d | |
zizmor: | |
name: Run zizmor 🌈 | |
runs-on: ubuntu-latest | |
permissions: | |
security-events: write | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
with: | |
persist-credentials: false | |
- name: Run zizmor 🌈 | |
uses: zizmorcore/zizmor-action@e673c3917a1aef3c65c972347ed84ccd013ecda4 # v0.2.0 | |
lint-branch-name: | |
needs: determine-test-scope | |
runs-on: ubuntu-latest | |
name: lint-branch-name | |
env: | |
PR_TITLE: ${{ github.event.pull_request.title }} | |
if: github.event_name == 'pull_request' | |
steps: | |
- name: extract-branch-name | |
id: extract-branch-name | |
run: | | |
BRANCH_NAME="${{ github.head_ref }}" | |
echo "Branch name: $BRANCH_NAME" | |
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT | |
- name: enforce-jira-key | |
id: enforce-jira-key | |
run: | | |
BRANCH_NAME="${{ steps.extract-branch-name.outputs.branch_name }}" | |
echo $BRANCH_NAME | |
JIRA_PATTERN='[A-Z]{2,}-[0-9]+' | |
# List of exempt prefixes (case-insensitive) | |
EXEMPT_PREFIXES=("chore" "hotfix" "daily-chore") | |
# Convert branch name to lowercase for comparison | |
BRANCH_LOWER=$(echo "$BRANCH_NAME" | tr '[:upper:]' '[:lower:]') | |
# Check if branch starts with any exempt prefix | |
for prefix in "${EXEMPT_PREFIXES[@]}"; do | |
if [[ "$BRANCH_LOWER" == $prefix* ]]; then | |
echo "ℹ️ Branch starts with '$prefix' - Jira key not required" | |
exit 0 | |
fi | |
done | |
if [[ "$BRANCH_NAME" =~ $JIRA_PATTERN ]]; then | |
echo "✅ Jira key found in branch name: ${BASH_REMATCH[0]}" | |
else | |
echo "❌ No Jira key found in branch name, checking PR name as fallback" | |
if [[ "$PR_TITLE" =~ $JIRA_PATTERN ]]; then | |
echo "✅ Jira key found in PR-title: ${BASH_REMATCH[0]}" | |
else | |
echo "❌ No Jira key found in branch name and PR title" | |
exit 1 | |
fi | |
fi | |
lint-commit-messages: | |
needs: determine-test-scope | |
runs-on: ubuntu-latest | |
name: lint-commit-messages | |
steps: | |
- name: Check out source code | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 # fetch all commits in the PR | |
- name: Setup node | |
uses: actions/setup-node@v4 | |
with: | |
node-version: lts/* | |
- name: Install commitlint | |
run: npm install -D @commitlint/cli @commitlint/config-conventional | |
- name: Print versions | |
run: | | |
git --version | |
node --version | |
npm --version | |
npx commitlint --version | |
- name: Check commit messages (merge_group) | |
if: github.event_name == 'merge_group' | |
run: | | |
# For merge groups, check the commits being merged | |
npx commitlint --from ${{ github.event.merge_group.base_sha }} --to ${{ github.event.merge_group.head_sha }} --verbose || { | |
echo "Commit message linting failed; please follow the conventional commits format at https://www.conventionalcommits.org/" | |
exit 1 | |
} | |
verify-schema-change: | |
name: verify-schema-change | |
needs: determine-test-scope | |
if: | | |
(( needs.determine-test-scope.outputs.local_tests == 'true' ) || | |
( needs.determine-test-scope.outputs.remote_tests == 'true' )) | |
runs-on: ubuntu-latest | |
container: ghcr.io/astral-sh/uv:debian | |
defaults: | |
run: | |
shell: bash | |
steps: | |
- name: checkout-branch | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ github.event.pull_request.head.ref }} | |
repository: ${{ github.event.pull_request.head.repo.full_name }} | |
fetch-depth: 0 | |
- name: git-config | |
run: | | |
cd $GITHUB_WORKSPACE | |
git config --global --add safe.directory $GITHUB_WORKSPACE | |
- name: install-depedencies | |
run: | | |
uv venv $GITHUB_WORKSPACE/.venv -p 3.11 | |
source $GITHUB_WORKSPACE/.venv/bin/activate | |
uv pip install -e "$GITHUB_WORKSPACE" | |
- name: get-tidy3d-version | |
id: get-version | |
run: | | |
source $GITHUB_WORKSPACE/.venv/bin/activate | |
version=$(python -c "import tidy3d; print(tidy3d.__version__)") | |
echo "tidy3d version is $version" | |
echo "version=$version" >> $GITHUB_OUTPUT | |
- name: verify-committed-schema | |
run: | | |
set -euo pipefail | |
echo "Regenerating docs-free canonical schemas into repo schemas/ ..." | |
source $GITHUB_WORKSPACE/.venv/bin/activate | |
python $GITHUB_WORKSPACE/scripts/regenerate_schema.py | |
echo "Verifying committed schemas match generated output..." | |
if ! git diff --name-status --exit-code -- schemas; then | |
echo "❌ Committed schemas are not up-to-date. See diff above." | |
exit 1 | |
fi | |
echo "✅ Committed schemas are up-to-date." | |
- name: run-schema-diff | |
id: schema-diff | |
run: | | |
set -euo pipefail | |
cd "$GITHUB_WORKSPACE" | |
# Determine base repo/ref for PRs; default to current repo and 'develop' otherwise | |
BASE_REPO="${{ github.event.pull_request.base.repo.full_name }}" | |
BASE_REF="${{ github.event.pull_request.base.ref }}" | |
if [ -z "$BASE_REPO" ]; then | |
BASE_REPO="${{ github.repository }}" | |
fi | |
if [ -z "$BASE_REF" ]; then | |
BASE_REF="develop" | |
fi | |
echo "Fetching base branch $BASE_REPO@$BASE_REF (shallow)..." | |
git remote add upstream "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${BASE_REPO}.git" || true | |
git fetch --no-tags --prune --depth=1 upstream "+refs/heads/${BASE_REF}:refs/remotes/upstream/${BASE_REF}" | |
# Name-status diff between base and head limited to schemas/ | |
DIFF_OUTPUT=$(git diff --name-status "upstream/${BASE_REF}...HEAD" -- schemas || true) | |
if [ -z "$DIFF_OUTPUT" ]; then | |
echo "✅ No schema changes relative to ${BASE_REF}." | |
echo "changed=false" >> "$GITHUB_OUTPUT" | |
exit 0 | |
fi | |
echo "Schema changes detected relative to ${BASE_REF}." | |
echo "changed=true" >> "$GITHUB_OUTPUT" | |
# Summarize changes | |
{ | |
echo "### Schema Change Summary" | |
echo "| Status | File |" | |
echo "|:---:|:---|" | |
} >> "$GITHUB_STEP_SUMMARY" | |
while IFS=$'\t' read -r status file; do | |
# Map short status to human-friendly | |
case "$status" in | |
A|AM) label="Added 🟢" ;; | |
M|MM) label="Modified 🟡" ;; | |
D) label="Removed 🔴" ;; | |
R*) label="Renamed 🟠" ;; | |
*) label="$status" ;; | |
esac | |
echo "| $label | \`$file\` |" >> "$GITHUB_STEP_SUMMARY" | |
done <<< "$DIFF_OUTPUT" | |
- name: verify-allowed-changes | |
if: steps.schema-diff.outputs.changed == 'true' | |
run: | | |
set -e | |
version="${{ steps.get-version.outputs.version }}" | |
if [[ "$version" == *rc* ]]; then | |
echo "✅ Passing: Schema changed on a release candidate version ($version), which is permitted." | |
else | |
echo "❌ Failing: Schema changed on a non-rc release version ($version)." | |
exit 1 | |
fi | |
local-tests: | |
# Run on open PRs OR when manually triggered with local_tests=true | |
needs: determine-test-scope | |
if: needs.determine-test-scope.outputs.local_tests == 'true' | |
name: python-${{ matrix.python-version }}-self-hosted-runner | |
runs-on: [ slurm-runner, 4xcpu, container=ghcr.io/astral-sh/uv:debian ] | |
concurrency: | |
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.event_name }}-${{ matrix.python-version }}-local | |
cancel-in-progress: true | |
strategy: | |
matrix: | |
python-version: ['3.10', '3.13'] | |
defaults: | |
run: | |
shell: bash | |
env: # Set environment variables for the whole job | |
PIP_ONLY_BINARY: gdstk | |
MPLBACKEND: agg | |
steps: | |
- uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 # Required 0 for diff report. | |
submodules: false | |
- name: install-project | |
env: | |
PYTHON_VERSION: ${{ matrix.python-version }} | |
run: | | |
if [ -f /.dockerenv ]; then | |
echo "Running inside a Docker container (detected via /.dockerenv)" | |
else | |
echo "Not running inside a Docker container (/.dockerenv not found)" | |
fi | |
uv venv -p $PYTHON_VERSION ${GITHUB_WORKSPACE}/.venv | |
source ${GITHUB_WORKSPACE}/.venv/bin/activate | |
which python | |
which uv | |
python --version | |
uv pip list | |
uv pip install gdstk --only-binary gdstk | |
uv pip install -e ".[dev]" | |
echo "Testing vtk is correctly installed." | |
python -c "import vtk" | |
- name: run-tests-coverage | |
env: | |
PYTHONUNBUFFERED: "1" | |
run: | | |
source ${GITHUB_WORKSPACE}/.venv/bin/activate | |
# pytest --cov=tidy3d -rF --tb=short tests/_test_data/_test_datasets_no_vtk.py | |
pytest --cov=tidy3d -rF --tb=short tests | |
coverage report -m | |
coverage xml -o ${GITHUB_WORKSPACE}/coverage.xml | |
TOTAL_COVERAGE=$(coverage report --format=total) | |
echo "total=$TOTAL_COVERAGE" >> "$GITHUB_ENV" | |
echo "### Total coverage: ${TOTAL_COVERAGE}%" | |
- name: diff-coverage-report | |
if: >- | |
matrix.python-version == '3.13' && | |
github.event_name == 'pull_request' && | |
!contains(github.event.pull_request.labels.*.name, 'ignore_diff_coverage') | |
run: | | |
source ${GITHUB_WORKSPACE}/.venv/bin/activate | |
git config --global --add safe.directory ${GITHUB_WORKSPACE} | |
diff-cover ${GITHUB_WORKSPACE}/coverage.xml \ | |
--compare-branch origin/${{ github.event.pull_request.base.ref }} \ | |
--format markdown:diff-coverage.md | |
- uses: actions/github-script@v7 | |
if: >- | |
matrix.python-version == '3.13' && | |
github.event_name == 'pull_request' && | |
!contains(github.event.pull_request.labels.*.name, 'ignore_diff_coverage') && | |
github.event.pull_request.head.repo.fork == false | |
with: | |
result-encoding: string | |
script: | | |
const fs = require('fs'); | |
const marker = '<!-- diff-cover-report -->'; | |
const body = fs.readFileSync('diff-coverage.md','utf8'); | |
const report = `${marker}\n${body}`; | |
const {data:comments}=await github.rest.issues.listComments({ | |
owner:context.repo.owner, | |
repo:context.repo.repo, | |
issue_number:context.issue.number, | |
}); | |
const existing = comments.find(c=>c.body.startsWith(marker)); | |
if(existing) { | |
await github.rest.issues.updateComment({ | |
owner:context.repo.owner, | |
repo:context.repo.repo, | |
comment_id:existing.id, | |
body:report, | |
}); | |
} else { | |
await github.rest.issues.createComment({ | |
owner:context.repo.owner, | |
repo:context.repo.repo, | |
issue_number:context.issue.number, | |
body:report, | |
}); | |
} | |
remote-tests: | |
# Run tests on a push event OR a workflow dispatch with remote_tests | |
needs: determine-test-scope | |
if: needs.determine-test-scope.outputs.remote_tests == 'true' | |
name: python-${{ matrix.python-version }}-${{ matrix.platform }} | |
runs-on: ${{ matrix.platform }} | |
permissions: | |
contents: read | |
pull-requests: write | |
concurrency: | |
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}-${{ github.event_name }}-${{ matrix.platform }}-${{ matrix.python-version }}-remote | |
cancel-in-progress: true | |
strategy: | |
matrix: | |
python-version: ['3.10', '3.11', '3.12', '3.13'] | |
platform: [windows-latest, ubuntu-latest, macos-latest] | |
defaults: | |
run: | |
shell: bash | |
env: # Set environment variables for the whole job | |
PIP_ONLY_BINARY: gdstk | |
MPLBACKEND: agg | |
steps: | |
- uses: actions/checkout@v4 | |
with: | |
fetch-depth: 1 | |
submodules: false | |
- name: install-poetry | |
uses: snok/install-poetry@v1 | |
with: | |
version: 2.1.1 | |
virtualenvs-create: true | |
virtualenvs-in-project: true | |
- name: set-python-${{ matrix.python-version }} | |
uses: actions/setup-python@v4 | |
with: | |
python-version: ${{ matrix.python-version }} | |
- name: install-project | |
shell: bash | |
if: ${{ matrix.platform }} != "windows-latest" | |
run: | | |
poetry --version | |
python --version | |
python -m venv .venv | |
if [[ "${{ runner.os }}" == "Windows" ]]; then | |
source .venv/Scripts/activate | |
python --version | |
else | |
source .venv/bin/activate | |
which python | |
fi | |
poetry env use python | |
poetry env info | |
poetry run pip install --upgrade pip wheel setuptools | |
poetry run pip install gdstk --only-binary gdstk | |
poetry install -E dev | |
- name: run-doctests | |
run: | | |
poetry run pytest -rF --tb=short tidy3d | |
- name: run-tests-coverage | |
env: | |
PYTHONUNBUFFERED: "1" | |
run: | | |
poetry run pytest --cov=tidy3d -rF --tb=short tests/_test_data/_test_datasets_no_vtk.py | |
poetry run pytest --cov=tidy3d -rF --tb=short tests | |
poetry run coverage report -m | |
TOTAL_COVERAGE=$(poetry run coverage report --format=total) | |
echo "total=$TOTAL_COVERAGE" >> "$GITHUB_ENV" | |
echo "### Total coverage: ${TOTAL_COVERAGE}%" | |
- name: create-badge | |
if: ${{ github.ref == 'refs/heads/develop' }} | |
# https://gist.githubusercontent.com/nedbat/8c6980f77988a327348f9b02bbaf67f5 | |
uses: schneegans/[email protected] | |
with: | |
auth: ${{ secrets.GH_TIDY3D_COVERAGE_GIST }} | |
gistID: 4702549574741e87deaadba436218ebd | |
filename: tidy3d_extension.json | |
label: Coverage | |
message: ${{ env.total }}% | |
minColorRange: 60 | |
maxColorRange: 95 | |
valColorRange: ${{ env.total }} | |
style: "for-the-badge" | |
pr-requirements-pass: | |
name: pr-requirements-pass | |
if: | | |
always() && | |
((github.event_name == 'pull_request') || (github.event_name == 'pull_request_review') || (github.event_name == 'merge_group' )) && | |
(( needs.determine-test-scope.outputs.pr_approval_state == 'true' ) && | |
( needs.determine-test-scope.outputs.local_tests == 'true' ) || | |
( needs.determine-test-scope.outputs.remote_tests == 'true' )) | |
needs: [local-tests, remote-tests, lint, verify-schema-change, lint-commit-messages, lint-branch-name, zizmor] | |
runs-on: ubuntu-latest | |
steps: | |
- name: check-passing-remote-tests | |
run: | | |
echo "Local tests result: ${{ needs.local-tests.result }}" | |
echo "Remote tests result: ${{ needs.remote-tests.result }}" | |
if [[ "${{ needs.lint.result }}" != 'success' ]]; then | |
echo "❌ Linting failed or was skipped." | |
exit 1 | |
elif [[ "${{ needs.verify-schema-change.result }}" != 'success' ]]; then | |
echo "❌ verify-schema-change failed or was skipped." | |
exit 1 | |
elif [[ "${{ needs.remote-tests.result }}" != 'success' ]]; then | |
echo "❌ remote-tests failed or were skipped." | |
exit 1 # Given remote-tests always run after a PR is approved | |
elif [[ "${{ needs.local-tests.result }}" != 'success' ]]; then | |
echo "❌ local-tests failed or were skipped." | |
elif [[ "${{ github.event_name }}" == 'merge_group' && "${{ needs.lint-commit-messages.result }}" != 'success' ]]; then | |
echo "❌ Linting of commit messages failed or was skipped." | |
exit 1 | |
elif [[ "${{ github.event_name }}" == 'pull_request' && "${{ needs.lint-branch-name.result }}" != 'success' ]]; then | |
echo "❌ Linting of branch name failed." | |
exit 1 | |
elif [[ "${{ needs.zizmor.result }}" != 'success' ]]; then | |
echo "❌ Static check of github actions with zizmor failed." | |
exit 1 | |
fi | |
echo "✅ All required test jobs passed!" |