Skip to content

chore(deps-dev): bump the python group across 1 directory with 10 updates #9287

chore(deps-dev): bump the python group across 1 directory with 10 updates

chore(deps-dev): bump the python group across 1 directory with 10 updates #9287

name: "public/tidy3d/python-client-tests"
on:
merge_group:
push:
branches:
- develop
workflow_dispatch:
inputs:
remote_tests:
description: 'remote-tests'
type: boolean
default: true
local_tests:
description: 'local-tests'
type: boolean
default: false
cli_tests:
description: 'develop-cli'
type: boolean
default: false
submodule_tests:
description: 'submodule-tests'
type: boolean
default: false
version_match_tests:
description: 'version-consistency-checks'
type: boolean
default: false
extras_integration_tests:
description: 'integration-tidy3d-extras'
type: boolean
default: false
test_type:
description: 'test-type (basic or full)'
type: choice
options:
- basic
- full
default: 'basic'
test_selection:
description: 'test-selection (testmon or full)'
type: choice
options:
- testmon
- full
default: 'testmon'
release_tag:
description: 'Release Tag (v2.10.0, v2.10.0rc1)'
required: false
type: string
default: ''
workflow_call:
inputs:
remote_tests:
description: 'remote-tests'
type: boolean
required: false
default: true
local_tests:
description: 'local-tests'
type: boolean
required: false
default: true
cli_tests:
description: 'Run develop-cli tests'
type: boolean
required: false
default: false
submodule_tests:
description: 'Run submodule tests'
type: boolean
required: false
default: false
version_match_tests:
description: 'Run version consistency checks'
type: boolean
required: false
default: false
extras_integration_tests:
description: 'Run tidy3d-extras integration tests'
type: boolean
required: false
default: false
test_type:
description: 'Test type for extras integration tests (basic or full)'
type: string
required: false
default: 'basic'
test_selection:
description: 'Test selection mode for local and remote tests (testmon or full)'
type: string
required: false
default: 'testmon'
release_tag:
description: 'Release Tag (v2.10.0, v2.10.0rc1)'
required: false
type: string
default: ''
outputs:
workflow_success:
description: 'Overall test workflow success status'
value: ${{ jobs.workflow-validation.result == 'success' }}
pull_request:
branches:
- latest
- develop
- 'pre/*'
types: ['opened', 'reopened', 'synchronize', 'ready_for_review', 'edited']
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref == 'refs/heads/develop' }}
env:
VERIFICATIONS_PY_VERSION: '3.11'
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 }}
cli_tests: ${{ steps.determine-test-type.outputs.cli_tests }}
submodule_tests: ${{ steps.determine-test-type.outputs.submodule_tests }}
version_match_tests: ${{ steps.determine-test-type.outputs.version_match_tests }}
extras_integration_tests: ${{ steps.determine-test-type.outputs.extras_integration_tests }}
test_type: ${{ steps.determine-test-type.outputs.test_type }}
test_selection: ${{ steps.determine-test-type.outputs.test_selection }}
steps:
- 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 || inputs.local_tests }}
INPUT_REMOTE: ${{ github.event.inputs.remote_tests || inputs.remote_tests }}
INPUT_CLI: ${{ github.event.inputs.cli_tests || inputs.cli_tests }}
INPUT_SUBMODULE: ${{ github.event.inputs.submodule_tests || inputs.submodule_tests }}
INPUT_VERSION_MATCH: ${{ github.event.inputs.version_match_tests || inputs.version_match_tests }}
INPUT_EXTRAS_INTEGRATION: ${{ github.event.inputs.extras_integration_tests || inputs.extras_integration_tests }}
INPUT_TEST_TYPE: ${{ github.event.inputs.test_type || inputs.test_type }}
INPUT_TEST_SELECTION: ${{ github.event.inputs.test_selection || inputs.test_selection }}
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 "Input cli: $INPUT_CLI"
echo "Input submodule: $INPUT_SUBMODULE"
echo "Input version_match: $INPUT_VERSION_MATCH"
echo "Input extras_integration: $INPUT_EXTRAS_INTEGRATION"
echo "Input test_type: $INPUT_TEST_TYPE"
echo "Input test_selection: $INPUT_TEST_SELECTION"
remote_tests=false
local_tests=false
cli_tests=false
submodule_tests=false
version_match_tests=false
extras_integration_tests=false
test_type="basic"
test_selection="testmon"
# Workflow_dispatch and workflow_call input override
if [[ "$EVENT_NAME" == "workflow_dispatch" || "$EVENT_NAME" == "workflow_call" ]]; then
# Each option is self contained
if [[ "$INPUT_REMOTE" == "true" ]]; then
remote_tests=true
fi
if [[ "$INPUT_LOCAL" == "true" ]]; then
local_tests=true
fi
if [[ "$INPUT_CLI" == "true" ]]; then
cli_tests=true
fi
if [[ "$INPUT_SUBMODULE" == "true" ]]; then
submodule_tests=true
fi
if [[ "$INPUT_VERSION_MATCH" == "true" ]]; then
version_match_tests=true
fi
if [[ "$INPUT_EXTRAS_INTEGRATION" == "true" ]]; then
extras_integration_tests=true
fi
if [[ -n "$INPUT_TEST_SELECTION" ]]; then
test_selection="$INPUT_TEST_SELECTION"
fi
fi
# All PRs that have been triggered need local tests (remote reserved for merge queue/manual)
if [[ "$EVENT_NAME" == "pull_request" ]]; then
local_tests=true
test_selection="testmon"
fi
if [[ "$EVENT_NAME" == "merge_group" ]]; then
local_tests=true
remote_tests=true
extras_integration_tests=true
test_type="basic"
test_selection="full"
fi
if [[ "$EVENT_NAME" == "push" ]]; then
local_tests=true
remote_tests=true
extras_integration_tests=true
test_type="basic"
test_selection="full"
fi
# Set test_type based on input or event
if [[ -n "$INPUT_TEST_TYPE" ]]; then
test_type="$INPUT_TEST_TYPE"
fi
echo "local_tests=$local_tests" >> $GITHUB_OUTPUT
echo "remote_tests=$remote_tests" >> $GITHUB_OUTPUT
echo "cli_tests=$cli_tests" >> $GITHUB_OUTPUT
echo "submodule_tests=$submodule_tests" >> $GITHUB_OUTPUT
echo "version_match_tests=$version_match_tests" >> $GITHUB_OUTPUT
echo "extras_integration_tests=$extras_integration_tests" >> $GITHUB_OUTPUT
echo "test_type=$test_type" >> $GITHUB_OUTPUT
echo "test_selection=$test_selection" >> $GITHUB_OUTPUT
echo "local_tests=$local_tests"
echo "remote_tests=$remote_tests"
echo "cli_tests=$cli_tests"
echo "submodule_tests=$submodule_tests"
echo "version_match_tests=$version_match_tests"
echo "extras_integration_tests=$extras_integration_tests"
echo "test_type=$test_type"
echo "test_selection=$test_selection"
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:
PIP_ONLY_BINARY: gdstk
MPLBACKEND: agg
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag || inputs.release_tag }}
TEST_SELECTION: ${{ needs.determine-test-scope.outputs.test_selection }}
permissions:
pull-requests: write
steps:
- name: checkout-head
if: ${{ !env.RELEASE_TAG }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
submodules: false
persist-credentials: false
lfs: true
- name: checkout-tag
if: ${{ env.RELEASE_TAG }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: refs/tags/${{ env.RELEASE_TAG }}
fetch-depth: 0
submodules: false
persist-credentials: false
lfs: true
- name: compute-testmon-cache-key
id: testmon-cache-key
if: needs.determine-test-scope.outputs.test_selection == 'testmon' || github.event_name == 'merge_group'
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch || 'develop' }}
run: |
default_branch="$DEFAULT_BRANCH"
case "$default_branch" in
''|*[!A-Za-z0-9._/-]*)
echo "::warning::Invalid default branch '$default_branch'; using 'develop'."
default_branch="develop"
;;
esac
default_branch_sha=$(git ls-remote --heads origin "refs/heads/${default_branch}" | awk '{print $1}')
if [[ -z "${default_branch_sha}" ]]; then
echo "::warning::Unable to resolve SHA for default branch '${default_branch}'."
default_branch_sha="unknown"
fi
dep_hash=$(
{
git show HEAD:pyproject.toml
if git cat-file -e HEAD:uv.lock 2>/dev/null; then
git show HEAD:uv.lock
else
git show HEAD:poetry.lock
fi
} | sha256sum | awk '{print $1}'
)
cache_layout="v2"
if [[ "${GITHUB_EVENT_NAME}" == "merge_group" ]]; then
cache_sha="${GITHUB_SHA}"
else
cache_sha="${default_branch_sha}"
fi
echo "default_branch=$default_branch" >> "$GITHUB_OUTPUT"
echo "default_branch_sha=$default_branch_sha" >> "$GITHUB_OUTPUT"
echo "dep_hash=$dep_hash" >> "$GITHUB_OUTPUT"
echo "cache_layout=$cache_layout" >> "$GITHUB_OUTPUT"
echo "cache_sha=$cache_sha" >> "$GITHUB_OUTPUT"
- name: restore-testmon-cache
id: testmon-cache-restore
if: needs.determine-test-scope.outputs.test_selection == 'testmon' || github.event_name == 'merge_group'
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: .testmondata
key: testmon-local-${{ steps.testmon-cache-key.outputs.cache_layout }}-${{ runner.os }}-py${{ matrix.python-version }}-${{ steps.testmon-cache-key.outputs.default_branch }}-${{ steps.testmon-cache-key.outputs.dep_hash }}-${{ steps.testmon-cache-key.outputs.cache_sha }}
restore-keys: |
testmon-local-${{ steps.testmon-cache-key.outputs.cache_layout }}-${{ runner.os }}-py${{ matrix.python-version }}-${{ steps.testmon-cache-key.outputs.default_branch }}-${{ steps.testmon-cache-key.outputs.dep_hash }}-
- name: telemetry-cache-exact-hit
if: >-
(needs.determine-test-scope.outputs.test_selection == 'testmon' || github.event_name == 'merge_group') &&
steps.testmon-cache-restore.outputs.cache-hit == 'true'
run: echo "testmon cache exact hit"
- name: telemetry-cache-fallback-hit
if: >-
(needs.determine-test-scope.outputs.test_selection == 'testmon' || github.event_name == 'merge_group') &&
steps.testmon-cache-restore.outputs.cache-hit != 'true' &&
steps.testmon-cache-restore.outputs.cache-matched-key != ''
run: echo "testmon cache fallback hit"
- name: telemetry-cache-miss
if: >-
(needs.determine-test-scope.outputs.test_selection == 'testmon' || github.event_name == 'merge_group') &&
steps.testmon-cache-restore.outputs.cache-matched-key == ''
run: echo "testmon cache miss"
- 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: determine-pytest-selection-flags
id: pytest-selection-flags
env:
EVENT_NAME: ${{ github.event_name }}
run: |
if [[ "${TEST_SELECTION}" == "testmon" ]]; then
echo "flags=--testmon --testmon-forceselect" >> "$GITHUB_OUTPUT"
echo "mode=testmon_forceselect" >> "$GITHUB_OUTPUT"
echo "Using pytest testmon selection mode."
elif [[ "${TEST_SELECTION}" == "full" && "${EVENT_NAME}" == "merge_group" ]]; then
echo "flags=--testmon-noselect" >> "$GITHUB_OUTPUT"
echo "mode=full_with_testmon_collection" >> "$GITHUB_OUTPUT"
echo "Using full test execution with testmon collection mode."
else
echo "flags=--no-testmon" >> "$GITHUB_OUTPUT"
echo "mode=full_no_testmon" >> "$GITHUB_OUTPUT"
echo "Using full pytest execution mode."
fi
- name: telemetry-selection-testmon
if: steps.pytest-selection-flags.outputs.mode == 'testmon_forceselect'
run: echo "pytest selection testmon forceselect"
- name: telemetry-selection-full-with-testmon-collection
if: steps.pytest-selection-flags.outputs.mode == 'full_with_testmon_collection'
run: echo "pytest selection full with testmon collection"
- name: telemetry-selection-full-no-testmon
if: steps.pytest-selection-flags.outputs.mode == 'full_no_testmon'
run: echo "pytest selection full no testmon"
- name: run-tests-coverage
env:
PYTHONUNBUFFERED: "1"
PYTEST_SELECTION_FLAGS: ${{ steps.pytest-selection-flags.outputs.flags }}
run: |
source ${GITHUB_WORKSPACE}/.venv/bin/activate
# pytest --cov=tidy3d -rF --tb=short tests/_test_data/_test_datasets_no_vtk.py
pytest ${PYTEST_SELECTION_FLAGS} --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: stage-testmon-cache-candidate
if: success() && github.event_name == 'merge_group' && hashFiles('.testmondata') != ''
env:
CACHE_KEY: testmon-local-${{ steps.testmon-cache-key.outputs.cache_layout }}-${{ runner.os }}-py${{ matrix.python-version }}-${{ steps.testmon-cache-key.outputs.default_branch }}-${{ steps.testmon-cache-key.outputs.dep_hash }}-${{ steps.testmon-cache-key.outputs.cache_sha }}
run: |
set -euo pipefail
candidate_dir="$RUNNER_TEMP/testmon-cache-candidate-local-py${{ matrix.python-version }}"
rm -rf "$candidate_dir"
mkdir -p "$candidate_dir"
cp -R .testmondata "$candidate_dir/.testmondata"
cat > "$candidate_dir/metadata.json" <<EOF
{
"cache_key": "${CACHE_KEY}",
"job_class": "local",
"runner_os": "${{ runner.os }}",
"python_version": "${{ matrix.python-version }}"
}
EOF
- name: upload-testmon-cache-candidate
if: success() && github.event_name == 'merge_group' && hashFiles('.testmondata') != ''
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: testmon-cache-candidate-local-${{ runner.os }}-py${{ matrix.python-version }}-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/testmon-cache-candidate-local-py${{ matrix.python-version }}
include-hidden-files: true
retention-days: 1
- name: save-testmon-cache
if: success() && github.event_name == 'merge_group' && steps.testmon-cache-restore.outputs.cache-hit != 'true' && hashFiles('.testmondata') != ''
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: .testmondata
key: testmon-local-${{ steps.testmon-cache-key.outputs.cache_layout }}-${{ runner.os }}-py${{ matrix.python-version }}-${{ steps.testmon-cache-key.outputs.default_branch }}-${{ steps.testmon-cache-key.outputs.dep_hash }}-${{ steps.testmon-cache-key.outputs.cache_sha }}
- 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')
env:
GITHUB_EVENT_PULL_REQUEST_BASE_REF: ${{ github.event.pull_request.base.ref }}
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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
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:
PIP_ONLY_BINARY: gdstk
MPLBACKEND: agg
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag || inputs.release_tag }}
TEST_SELECTION: ${{ needs.determine-test-scope.outputs.test_selection }}
steps:
- name: checkout-head
if: ${{ !env.RELEASE_TAG }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
submodules: false
persist-credentials: false
lfs: true
- name: checkout-tag
if: ${{ env.RELEASE_TAG }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: refs/tags/${{ env.RELEASE_TAG }}
fetch-depth: 1
submodules: false
persist-credentials: false
lfs: true
- name: compute-testmon-cache-key
id: testmon-cache-key
if: needs.determine-test-scope.outputs.test_selection == 'testmon' || github.event_name == 'merge_group'
env:
DEFAULT_BRANCH: ${{ github.event.repository.default_branch || 'develop' }}
run: |
default_branch="$DEFAULT_BRANCH"
case "$default_branch" in
''|*[!A-Za-z0-9._/-]*)
echo "::warning::Invalid default branch '$default_branch'; using 'develop'."
default_branch="develop"
;;
esac
default_branch_sha=$(git ls-remote --heads origin "refs/heads/${default_branch}" | awk '{print $1}')
if [[ -z "${default_branch_sha}" ]]; then
echo "::warning::Unable to resolve SHA for default branch '${default_branch}'."
default_branch_sha="unknown"
fi
dep_hash=$(
{
git show HEAD:pyproject.toml
if git cat-file -e HEAD:uv.lock 2>/dev/null; then
git show HEAD:uv.lock
else
git show HEAD:poetry.lock
fi
} | sha256sum | awk '{print $1}'
)
cache_layout="v2"
if [[ "${GITHUB_EVENT_NAME}" == "merge_group" ]]; then
cache_sha="${GITHUB_SHA}"
else
cache_sha="${default_branch_sha}"
fi
echo "default_branch=$default_branch" >> "$GITHUB_OUTPUT"
echo "default_branch_sha=$default_branch_sha" >> "$GITHUB_OUTPUT"
echo "dep_hash=$dep_hash" >> "$GITHUB_OUTPUT"
echo "cache_layout=$cache_layout" >> "$GITHUB_OUTPUT"
echo "cache_sha=$cache_sha" >> "$GITHUB_OUTPUT"
- name: restore-testmon-cache
id: testmon-cache-restore
if: needs.determine-test-scope.outputs.test_selection == 'testmon' || github.event_name == 'merge_group'
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: .testmondata
key: testmon-remote-${{ steps.testmon-cache-key.outputs.cache_layout }}-${{ matrix.platform }}-py${{ matrix.python-version }}-${{ steps.testmon-cache-key.outputs.default_branch }}-${{ steps.testmon-cache-key.outputs.dep_hash }}-${{ steps.testmon-cache-key.outputs.cache_sha }}
restore-keys: |
testmon-remote-${{ steps.testmon-cache-key.outputs.cache_layout }}-${{ matrix.platform }}-py${{ matrix.python-version }}-${{ steps.testmon-cache-key.outputs.default_branch }}-${{ steps.testmon-cache-key.outputs.dep_hash }}-
- name: telemetry-cache-exact-hit
if: >-
(needs.determine-test-scope.outputs.test_selection == 'testmon' || github.event_name == 'merge_group') &&
steps.testmon-cache-restore.outputs.cache-hit == 'true'
run: echo "testmon cache exact hit"
- name: telemetry-cache-fallback-hit
if: >-
(needs.determine-test-scope.outputs.test_selection == 'testmon' || github.event_name == 'merge_group') &&
steps.testmon-cache-restore.outputs.cache-hit != 'true' &&
steps.testmon-cache-restore.outputs.cache-matched-key != ''
run: echo "testmon cache fallback hit"
- name: telemetry-cache-miss
if: >-
(needs.determine-test-scope.outputs.test_selection == 'testmon' || github.event_name == 'merge_group') &&
steps.testmon-cache-restore.outputs.cache-matched-key == ''
run: echo "testmon cache miss"
- name: set-python-${{ matrix.python-version }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ matrix.python-version }}
- name: install-uv
run: |
python -m pip install --upgrade pip
python -m pip install uv
- name: install-project
shell: bash
run: |
uv --version
python --version
uv sync --frozen --extra dev
PROJECT_PYTHON=$(uv run --frozen python -c "import sys; print(sys.executable)")
uv pip install --python "$PROJECT_PYTHON" --upgrade wheel setuptools
uv pip install --python "$PROJECT_PYTHON" gdstk --only-binary gdstk
- name: determine-pytest-selection-flags
id: pytest-selection-flags
env:
EVENT_NAME: ${{ github.event_name }}
run: |
if [[ "${TEST_SELECTION}" == "testmon" ]]; then
echo "flags=--testmon --testmon-forceselect" >> "$GITHUB_OUTPUT"
echo "mode=testmon_forceselect" >> "$GITHUB_OUTPUT"
echo "Using pytest testmon selection mode."
elif [[ "${TEST_SELECTION}" == "full" && "${EVENT_NAME}" == "merge_group" ]]; then
echo "flags=--testmon-noselect" >> "$GITHUB_OUTPUT"
echo "mode=full_with_testmon_collection" >> "$GITHUB_OUTPUT"
echo "Using full test execution with testmon collection mode."
else
echo "flags=--no-testmon" >> "$GITHUB_OUTPUT"
echo "mode=full_no_testmon" >> "$GITHUB_OUTPUT"
echo "Using full pytest execution mode."
fi
- name: telemetry-selection-testmon
if: steps.pytest-selection-flags.outputs.mode == 'testmon_forceselect'
run: echo "pytest selection testmon forceselect"
- name: telemetry-selection-full-with-testmon-collection
if: steps.pytest-selection-flags.outputs.mode == 'full_with_testmon_collection'
run: echo "pytest selection full with testmon collection"
- name: telemetry-selection-full-no-testmon
if: steps.pytest-selection-flags.outputs.mode == 'full_no_testmon'
run: echo "pytest selection full no testmon"
- name: run-doctests
env:
PYTEST_SELECTION_FLAGS: ${{ steps.pytest-selection-flags.outputs.flags }}
run: |
uv run --frozen pytest ${PYTEST_SELECTION_FLAGS} -rF --tb=short tidy3d
- name: run-tests-coverage
env:
PYTHONUNBUFFERED: "1"
PYTEST_SELECTION_FLAGS: ${{ steps.pytest-selection-flags.outputs.flags }}
run: |
uv run --frozen pytest ${PYTEST_SELECTION_FLAGS} --cov=tidy3d -rF --tb=short tests/_test_data/_test_datasets_no_vtk.py
uv run --frozen pytest ${PYTEST_SELECTION_FLAGS} --cov=tidy3d -rF --tb=short tests
uv run --frozen coverage report -m
TOTAL_COVERAGE=$(uv run --frozen coverage report --format=total)
echo "total=$TOTAL_COVERAGE" >> "$GITHUB_ENV"
echo "### Total coverage: ${TOTAL_COVERAGE}%"
- name: stage-testmon-cache-candidate
if: success() && github.event_name == 'merge_group' && hashFiles('.testmondata') != ''
env:
CACHE_KEY: testmon-remote-${{ steps.testmon-cache-key.outputs.cache_layout }}-${{ matrix.platform }}-py${{ matrix.python-version }}-${{ steps.testmon-cache-key.outputs.default_branch }}-${{ steps.testmon-cache-key.outputs.dep_hash }}-${{ steps.testmon-cache-key.outputs.cache_sha }}
run: |
set -euo pipefail
candidate_dir="$RUNNER_TEMP/testmon-cache-candidate-remote-${{ matrix.platform }}-py${{ matrix.python-version }}"
rm -rf "$candidate_dir"
mkdir -p "$candidate_dir"
cp -R .testmondata "$candidate_dir/.testmondata"
cat > "$candidate_dir/metadata.json" <<EOF
{
"cache_key": "${CACHE_KEY}",
"job_class": "remote",
"platform": "${{ matrix.platform }}",
"python_version": "${{ matrix.python-version }}"
}
EOF
- name: upload-testmon-cache-candidate
if: success() && github.event_name == 'merge_group' && hashFiles('.testmondata') != ''
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: testmon-cache-candidate-remote-${{ matrix.platform }}-py${{ matrix.python-version }}-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/testmon-cache-candidate-remote-${{ matrix.platform }}-py${{ matrix.python-version }}
include-hidden-files: true
retention-days: 1
- name: save-testmon-cache
if: success() && github.event_name == 'merge_group' && steps.testmon-cache-restore.outputs.cache-hit != 'true' && hashFiles('.testmondata') != ''
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: .testmondata
key: testmon-remote-${{ steps.testmon-cache-key.outputs.cache_layout }}-${{ matrix.platform }}-py${{ matrix.python-version }}-${{ steps.testmon-cache-key.outputs.default_branch }}-${{ steps.testmon-cache-key.outputs.dep_hash }}-${{ steps.testmon-cache-key.outputs.cache_sha }}
- name: create-badge
if: ${{ github.ref == 'refs/heads/develop' }}
# https://gist.githubusercontent.com/nedbat/8c6980f77988a327348f9b02bbaf67f5
uses: schneegans/dynamic-badges-action@e9a478b16159b4d31420099ba146cdc50f134483 # v1.7.0
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"
develop-cli-tests:
name: develop-cli-tests
needs: determine-test-scope
if: needs.determine-test-scope.outputs.cli_tests == 'true'
uses: ./.github/workflows/tidy3d-python-client-develop-cli.yml
with:
release_tag: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag || inputs.release_tag }}
verify-version-consistency:
name: verify-version-consistency
runs-on: ubuntu-latest
needs: determine-test-scope
if: needs.determine-test-scope.outputs.version_match_tests == 'true'
steps:
- name: checkout-code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag || inputs.release_tag) || github.ref }}
persist-credentials: false
- name: check-version-consistency
env:
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag || inputs.release_tag }}
run: |
set -e
echo "=== Verifying Version Consistency ==="
echo ""
# Extract version from pyproject.toml
PYPROJECT_VERSION=$(grep '^version = ' pyproject.toml | head -n 1 | sed 's/version = "\(.*\)"/\1/')
echo "pyproject.toml version: $PYPROJECT_VERSION"
# Extract version from tidy3d/version.py
VERSION_PY=$(grep '__version__ = ' tidy3d/version.py | sed 's/__version__ = "\(.*\)"/\1/')
echo "tidy3d/version.py version: $VERSION_PY"
echo ""
# Compare versions
if [[ "$PYPROJECT_VERSION" != "$VERSION_PY" ]]; then
echo "❌ ERROR: Version mismatch detected!"
echo " pyproject.toml: $PYPROJECT_VERSION"
echo " tidy3d/version.py: $VERSION_PY"
echo ""
echo "These versions must match before release."
echo "Please update both files to the same version."
exit 1
fi
echo "✅ Version consistency check passed: $PYPROJECT_VERSION"
echo ""
# If release tag provided, validate it matches the version
if [[ -n "$RELEASE_TAG" ]]; then
echo "=== Validating Release Tag ==="
echo "Release tag: $RELEASE_TAG"
# Strip 'v' prefix from tag if present
TAG_VERSION="${RELEASE_TAG#v}"
echo "Tag version (without 'v'): $TAG_VERSION"
if [[ "$TAG_VERSION" != "$PYPROJECT_VERSION" ]]; then
echo "❌ ERROR: Release tag does not match package version!"
echo " Release tag: $RELEASE_TAG (version: $TAG_VERSION)"
echo " Package version: $PYPROJECT_VERSION"
echo ""
echo "The release tag should be 'v$PYPROJECT_VERSION'"
exit 1
fi
echo "✅ Release tag matches package version"
fi
echo ""
echo "=== Version Checks Passed ==="
test-submodules:
name: test-submodules
runs-on: ubuntu-latest
needs: determine-test-scope
if: ${{ always() && (needs.determine-test-scope.outputs.submodule_tests == 'true') && (github.event.inputs.release_tag || inputs.release_tag) && !contains(github.event.inputs.release_tag || inputs.release_tag, 'rc') }}
env:
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag || inputs.release_tag }}
steps:
- name: checkout-head
if: ${{ !env.RELEASE_TAG }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: 'recursive'
fetch-depth: 0
persist-credentials: true
- name: checkout-tag
if: ${{ env.RELEASE_TAG }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ env.RELEASE_TAG }}
submodules: 'recursive'
fetch-depth: 0
persist-credentials: true
- name: initialize-submodules
run: |
git submodule update --init --recursive
- name: check-submodules-for-multiple-branches
shell: bash
run: |
BRANCHES=("develop") # Add your branches here
for BRANCH in "${BRANCHES[@]}"; do
echo "Analyzing branch: $BRANCH"
# Fetch all branches and tags
git fetch --all --verbose
# Checkout the branch
git checkout $BRANCH
NOTEBOOKS_PATH=docs/notebooks
FAQ_PATH=docs/faq
# Checking Notebooks submodule
echo "Checking $NOTEBOOKS_PATH for updates..."
cd $NOTEBOOKS_PATH
NOTEBOOKS_CURRENT_COMMIT=$(git rev-parse HEAD)
echo $(git fetch --all --verbose)
echo $(git remote get-url origin)
if git show-ref --verify refs/remotes/origin/$BRANCH; then
echo "Branch $BRANCH exists."
else
echo "::error::Branch $BRANCH does not exist on remote."
exit 1
fi
NOTEBOOKS_LATEST_COMMIT=$(git rev-parse refs/remotes/origin/${BRANCH})
echo "NOTEBOOKS_LATEST_COMMIT: $NOTEBOOKS_LATEST_COMMIT"
echo "NOTEBOOKS_CURRENT_COMMIT: $NOTEBOOKS_CURRENT_COMMIT"
cd ../..
if [ "$NOTEBOOKS_LATEST_COMMIT" != "$NOTEBOOKS_CURRENT_COMMIT" ]; then
echo "::error::Submodule $NOTEBOOKS_PATH is not up to date with the $BRANCH branch. Please update it."
exit 1
else
echo "Submodule $NOTEBOOKS_PATH is up to date with the $BRANCH branch."
fi
# Checking FAQs only on the develop branch
if [[ "$BRANCH" == "develop" ]]; then
echo "Checking $FAQ_PATH for updates..."
cd $FAQ_PATH
FAQ_CURRENT_COMMIT=$(git rev-parse HEAD)
echo $(git fetch --all --verbose)
echo $(git remote get-url origin)
FAQ_LATEST_COMMIT=$(git rev-parse refs/remotes/origin/develop)
echo "FAQ_LATEST_COMMIT: $FAQ_LATEST_COMMIT"
echo "FAQ_CURRENT_COMMIT: $FAQ_CURRENT_COMMIT"
cd ../..
if [ "$FAQ_LATEST_COMMIT" != "$FAQ_CURRENT_COMMIT" ]; then
echo "::error::Submodule $FAQ_PATH is not up to date. Please update it."
exit 1
else
echo "Submodule $FAQ_PATH is up to date."
fi
fi
done
echo ""
echo "=== Submodule Checks Passed ==="
extras-integration-tests:
name: extras-integration-tests
needs: determine-test-scope
if: needs.determine-test-scope.outputs.extras_integration_tests == 'true'
uses: ./.github/workflows/tidy3d-extras-python-client-tests-integration.yml
with:
release_tag: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release_tag || inputs.release_tag }}
test_type: ${{ needs.determine-test-scope.outputs.test_type }}
secrets: inherit # zizmor: ignore[secrets-inherit]
workflow-validation:
name: workflow-validation
if: always()
needs:
- determine-test-scope
- local-tests
- remote-tests
- develop-cli-tests
- verify-version-consistency
- test-submodules
- extras-integration-tests
runs-on: ubuntu-latest
steps:
- name: check-local-tests-result
if: ${{ needs.determine-test-scope.outputs.local_tests == 'true' && needs.local-tests.result != 'success' && needs.local-tests.result != 'skipped' }}
run: |
echo "❌ Local tests failed."
exit 1
- name: check-remote-tests-result
if: ${{ needs.determine-test-scope.outputs.remote_tests == 'true' && needs.remote-tests.result != 'success' && needs.remote-tests.result != 'skipped' }}
run: |
echo "❌ Remote tests failed."
exit 1
- name: check-cli-tests-result
if: ${{ needs.determine-test-scope.outputs.cli_tests == 'true' && needs.develop-cli-tests.result != 'success' && needs.develop-cli-tests.result != 'skipped' }}
run: |
echo "❌ CLI tests failed."
exit 1
- name: check-version-consistency-result
if: ${{ needs.determine-test-scope.outputs.version_match_tests == 'true' && needs.verify-version-consistency.result != 'success' && needs.verify-version-consistency.result != 'skipped' }}
run: |
echo "❌ Version consistency check failed."
exit 1
- name: check-submodule-tests-result
if: ${{ needs.determine-test-scope.outputs.submodule_tests == 'true' && needs.test-submodules.result != 'success' && needs.test-submodules.result != 'skipped' }}
run: |
echo "❌ Submodule tests failed."
exit 1
- name: check-extras-integration-tests-result
if: ${{ needs.determine-test-scope.outputs.extras_integration_tests == 'true' && needs.extras-integration-tests.result != 'success' && needs.extras-integration-tests.result != 'skipped' }}
run: |
echo "❌ tidy3d-extras integration tests failed."
exit 1
- name: all-checks-passed
if: ${{ success() }}
run: echo "✅ All required jobs passed!"
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.local_tests == 'true' || needs.determine-test-scope.outputs.remote_tests == 'true')
needs:
- determine-test-scope
- workflow-validation
runs-on: ubuntu-latest
steps:
- name: check-workflow-validation
if: ${{ needs.workflow-validation.result != 'success' }}
run: |
echo "❌ Workflow validation failed. See workflow-validation job for details."
exit 1
- name: all-checks-passed
if: ${{ success() }}
run: echo "✅ All required jobs passed!"