Merge branch 'main' into feat/topic-ribbon-example #84
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: Build and Test | ||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| python-version: | ||
| required: true | ||
| type: string | ||
| matplotlib-version: | ||
| required: true | ||
| type: string | ||
| test-mode: | ||
| required: false | ||
| type: string | ||
| default: full | ||
| test-nodeids: | ||
| required: false | ||
| type: string | ||
| default: "" | ||
| env: | ||
| LC_ALL: en_US.UTF-8 | ||
| LANG: en_US.UTF-8 | ||
| jobs: | ||
| build-ultraplot: | ||
| name: Test Python ${{ inputs.python-version }} with MPL ${{ inputs.matplotlib-version }} | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 60 | ||
| defaults: | ||
| run: | ||
| shell: bash -el {0} | ||
| env: | ||
| TEST_MODE: ${{ inputs.test-mode }} | ||
| TEST_NODEIDS: ${{ inputs.test-nodeids }} | ||
| PYTEST_WORKERS: 4 | ||
| steps: | ||
| - name: Set up swap space | ||
| uses: pierotofy/set-swap-space@master | ||
| with: | ||
| swap-size-gb: 10 | ||
| - name: Show system memory | ||
| run: | | ||
| echo "=== System Memory ===" | ||
| free -h | ||
| echo "" | ||
| echo "=== CPU Info ===" | ||
| nproc | ||
| cat /proc/cpuinfo | grep "model name" | head -1 | ||
| - uses: actions/checkout@v6 | ||
| with: | ||
| fetch-depth: 0 | ||
| - uses: mamba-org/setup-micromamba@v2.0.7 | ||
| with: | ||
| environment-file: ./environment.yml | ||
| init-shell: bash | ||
| condarc-file: ./.github/micromamba-condarc.yml | ||
| post-cleanup: none | ||
| create-args: >- | ||
| --verbose | ||
| python=${{ inputs.python-version }} | ||
| matplotlib=${{ inputs.matplotlib-version }} | ||
| cache-environment: true | ||
| cache-downloads: false | ||
| - name: Build Ultraplot | ||
| run: | | ||
| pip install --no-build-isolation --no-deps . | ||
| compare-baseline: | ||
| name: Compare baseline Python ${{ inputs.python-version }} with MPL ${{ inputs.matplotlib-version }} | ||
| runs-on: ubuntu-latest | ||
| continue-on-error: true | ||
| env: | ||
| IS_PR: ${{ github.event_name == 'pull_request' }} | ||
| TEST_MODE: ${{ inputs.test-mode }} | ||
| TEST_NODEIDS: ${{ inputs.test-nodeids }} | ||
| PYTEST_WORKERS: 4 | ||
| defaults: | ||
| run: | ||
| shell: bash -el {0} | ||
| steps: | ||
| - name: Set up swap space | ||
| uses: pierotofy/set-swap-space@master | ||
| with: | ||
| swap-size-gb: 10 | ||
| - name: Show system memory | ||
| run: | | ||
| echo "=== System Memory ===" | ||
| free -h | ||
| echo "" | ||
| echo "=== CPU Info ===" | ||
| nproc | ||
| cat /proc/cpuinfo | grep "model name" | head -1 | ||
| - uses: actions/checkout@v6 | ||
| - uses: mamba-org/setup-micromamba@v2.0.7 | ||
| with: | ||
| environment-file: ./environment.yml | ||
| init-shell: bash | ||
| condarc-file: ./.github/micromamba-condarc.yml | ||
| post-cleanup: none | ||
| create-args: >- | ||
| --verbose | ||
| python=${{ inputs.python-version }} | ||
| matplotlib=${{ inputs.matplotlib-version }} | ||
| cache-environment: true | ||
| cache-downloads: false | ||
| # Cache Baseline Figures (Restore step) | ||
| - name: Cache Baseline Figures | ||
| id: cache-baseline | ||
| uses: actions/cache@v5 | ||
| if: ${{ env.IS_PR }} | ||
| with: | ||
| path: ./ultraplot/tests/baseline # The directory to cache | ||
| # Key is based on OS, Python/Matplotlib versions, and the base commit SHA | ||
| key: ${{ runner.os }}-baseline-base-v2-${{ github.event.pull_request.base.sha }}-${{ inputs.python-version }}-${{ inputs.matplotlib-version }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-baseline-base-v2-${{ github.event.pull_request.base.sha }}-${{ inputs.python-version }}-${{ inputs.matplotlib-version }}- | ||
| # Conditional Baseline Generation (Only runs on cache miss) | ||
| - name: Generate baseline from main | ||
| # Skip this step if the cache was found (cache-hit is true) | ||
| if: steps.cache-baseline.outputs.cache-hit != 'true' || !env.IS_PR | ||
| run: | | ||
| mkdir -p ultraplot/tests/baseline | ||
| echo "TEST_MODE=${TEST_MODE}" | ||
| echo "IS_PR=${IS_PR}" | ||
| echo "PR_BASE_SHA=${{ github.event.pull_request.base.sha }}" | ||
| echo "TEST_NODEIDS=${TEST_NODEIDS}" | ||
| # Save PR-selected nodeids for reuse after checkout (if provided) | ||
| if [ "${TEST_MODE}" = "selected" ] && [ -n "${TEST_NODEIDS}" ]; then | ||
| python -c 'import json, os | ||
| raw = os.environ.get("TEST_NODEIDS", "").strip() | ||
| nodeids = [] | ||
| if raw and raw != "[]": | ||
| try: | ||
| parsed = json.loads(raw) | ||
| except json.JSONDecodeError: | ||
| parsed = raw.split() | ||
| if isinstance(parsed, str): | ||
| parsed = [parsed] | ||
| if isinstance(parsed, list): | ||
| nodeids = [item for item in parsed if isinstance(item, str) and item] | ||
| with open("/tmp/pr_selected_nodeids.txt", "w", encoding="utf-8") as fh: | ||
| for nodeid in nodeids: | ||
| fh.write(f"{nodeid}\n") | ||
| print(f"Selected nodeids parsed: {len(nodeids)}")' | ||
| else | ||
| : > /tmp/pr_selected_nodeids.txt | ||
| fi | ||
| # Checkout the base commit for PRs; otherwise regenerate from current ref | ||
| if [ -n "${{ github.event.pull_request.base.sha }}" ]; then | ||
| git fetch origin ${{ github.event.pull_request.base.sha }} | ||
| git checkout ${{ github.event.pull_request.base.sha }} | ||
| fi | ||
| # Install the Ultraplot version from the base branch's code | ||
| pip install --no-build-isolation --no-deps . | ||
| # Generate the baseline images and hash library | ||
| python -c "import ultraplot as plt; plt.config.Configurator()._save_yaml('ultraplot.yml')" | ||
| if [ "${TEST_MODE}" = "selected" ] && [ -s /tmp/pr_selected_nodeids.txt ]; then | ||
| status=0 | ||
| mapfile -t FILTERED_NODEIDS < <( | ||
| while IFS= read -r nodeid; do | ||
| [ -z "$nodeid" ] && continue | ||
| path="${nodeid%%::*}" | ||
| [ -f "$path" ] && printf '%s\n' "$nodeid" | ||
| done < /tmp/pr_selected_nodeids.txt | ||
| ) | ||
| echo "FILTERED_NODEIDS_BASE_COUNT=${#FILTERED_NODEIDS[@]}" | ||
| if [ "${#FILTERED_NODEIDS[@]}" -eq 0 ]; then | ||
| echo "No valid nodeids found on base; skipping baseline generation." | ||
| else | ||
| echo "=== Memory before baseline generation ===" && free -h | ||
| pytest -n ${PYTEST_WORKERS} --dist loadfile --tb=short --disable-warnings -W ignore \ | ||
| --mpl-generate-path=./ultraplot/tests/baseline/ \ | ||
| --mpl-default-style="./ultraplot.yml" \ | ||
| "${FILTERED_NODEIDS[@]}" || status=$? | ||
| echo "=== Memory after baseline generation ===" && free -h | ||
| if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then | ||
| echo "No tests collected from selected nodeids on base; skipping baseline generation." | ||
| status=0 | ||
| fi | ||
| fi | ||
| # Return to the PR branch before continuing | ||
| if [ -n "${{ github.event.pull_request.base.sha }}" ]; then | ||
| echo "Checking out PR branch: ${{ github.sha }}" | ||
| git checkout ${{ github.sha }} || echo "Warning: git checkout failed, but continuing" | ||
| fi | ||
| if [ "$status" -ne 0 ]; then | ||
| echo "Baseline generation failed with status $status" | ||
| exit "$status" | ||
| fi | ||
| else | ||
| echo "=== Memory before baseline generation ===" && free -h | ||
| pytest -n ${PYTEST_WORKERS} --dist loadfile --tb=short --disable-warnings -W ignore \ | ||
| --mpl-generate-path=./ultraplot/tests/baseline/ \ | ||
| --mpl-default-style="./ultraplot.yml" \ | ||
| ultraplot/tests | ||
| echo "=== Memory after baseline generation ===" && free -h | ||
| # Return to the PR branch for the rest of the job | ||
| if [ -n "${{ github.event.pull_request.base.sha }}" ]; then | ||
| echo "Checking out PR branch: ${{ github.sha }}" | ||
| git checkout ${{ github.sha }} || echo "Warning: git checkout failed, but continuing" | ||
| fi | ||
| fi | ||
| # Image Comparison (Uses cached or newly generated baseline) | ||
| - name: Image Comparison Ultraplot | ||
| run: | | ||
| # Re-install the Ultraplot version from the current PR branch | ||
| pip install --no-build-isolation --no-deps . | ||
| mkdir -p results | ||
| python -c "import ultraplot as plt; plt.config.Configurator()._save_yaml('ultraplot.yml')" | ||
| echo "TEST_MODE=${TEST_MODE}" | ||
| echo "TEST_NODEIDS=${TEST_NODEIDS}" | ||
| if [ "${TEST_MODE}" = "selected" ] && [ -s /tmp/pr_selected_nodeids.txt ]; then | ||
| status=0 | ||
| mapfile -t FILTERED_NODEIDS < <( | ||
| while IFS= read -r nodeid; do | ||
| [ -z "$nodeid" ] && continue | ||
| path="${nodeid%%::*}" | ||
| [ -f "$path" ] && printf '%s\n' "$nodeid" | ||
| done < /tmp/pr_selected_nodeids.txt | ||
| ) | ||
| echo "FILTERED_NODEIDS_PR_COUNT=${#FILTERED_NODEIDS[@]}" | ||
| if [ "${#FILTERED_NODEIDS[@]}" -eq 0 ]; then | ||
| echo "No valid nodeids found on PR branch; skipping image comparison." | ||
| exit 0 | ||
| else | ||
| echo "=== Memory before image comparison ===" && free -h | ||
| pytest -n ${PYTEST_WORKERS} --dist loadfile --tb=short --disable-warnings -W ignore \ | ||
| --mpl \ | ||
| --mpl-baseline-path=./ultraplot/tests/baseline \ | ||
| --mpl-results-path=./results/ \ | ||
| --mpl-generate-summary=html \ | ||
| --mpl-default-style="./ultraplot.yml" \ | ||
| "${FILTERED_NODEIDS[@]}" || status=$? | ||
| echo "=== Memory after image comparison ===" && free -h | ||
| if [ "$status" -eq 4 ] || [ "$status" -eq 5 ]; then | ||
| echo "No tests collected from selected nodeids; skipping image comparison." | ||
| status=0 | ||
| fi | ||
| fi | ||
| exit "$status" | ||
| else | ||
| echo "=== Memory before image comparison ===" && free -h | ||
| pytest -n ${PYTEST_WORKERS} --dist loadfile --tb=short --disable-warnings -W ignore \ | ||
| --mpl \ | ||
| --mpl-baseline-path=./ultraplot/tests/baseline \ | ||
| --mpl-results-path=./results/ \ | ||
| --mpl-generate-summary=html \ | ||
| --mpl-default-style="./ultraplot.yml" \ | ||
| ultraplot/tests | ||
| echo "=== Memory after image comparison ===" && free -h | ||
| fi | ||
| # Return the html output of the comparison even if failed | ||
| - name: Upload comparison failures | ||
| if: always() | ||
| uses: actions/upload-artifact@v6 | ||
| with: | ||
| name: failed-comparisons-${{ inputs.python-version }}-${{ inputs.matplotlib-version }}-${{ github.sha }} | ||
| path: results/* | ||
| if-no-files-found: ignore | ||