diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index c3fcb0ea9591..000000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,104 +0,0 @@ -# With infos from -# http://tjelvarolsson.com/blog/how-to-continuously-test-your-python-code-on-windows-using-appveyor/ -# https://packaging.python.org/en/latest/appveyor/ ---- - -# Backslashes in quotes need to be escaped: \ -> "\\" -branches: - except: - - /auto-backport-.*/ - - /^v\d+\.\d+\.[\dx]+-doc$/ - -skip_commits: - message: /\[ci doc\]/ - files: - - doc/ - - galleries/ - -clone_depth: 50 - -image: Visual Studio 2022 - -environment: - - global: - PYTHONFAULTHANDLER: 1 - PYTHONIOENCODING: UTF-8 - PYTEST_ARGS: -rfEsXR --numprocesses=auto --timeout=300 --durations=25 - --cov-report= --cov=lib --log-level=DEBUG - - matrix: - - PYTHON_VERSION: "3.11" - TEST_ALL: "yes" - -# We always use a 64-bit machine, but can build x86 distributions -# with the PYTHON_ARCH variable -platform: - - x64 - -# all our python builds have to happen in tests_script... -build: false - -cache: - - '%LOCALAPPDATA%\pip\Cache' - - '%USERPROFILE%\.cache\matplotlib' - -init: - - ps: - Invoke-Webrequest - -URI https://micro.mamba.pm/api/micromamba/win-64/latest - -OutFile C:\projects\micromamba.tar.bz2 - - ps: C:\PROGRA~1\7-Zip\7z.exe x C:\projects\micromamba.tar.bz2 -aoa -oC:\projects\ - - ps: C:\PROGRA~1\7-Zip\7z.exe x C:\projects\micromamba.tar -ttar -aoa -oC:\projects\ - - 'set PATH=C:\projects\Library\bin;%PATH%' - - micromamba shell init --shell cmd.exe - - micromamba config set always_yes true - - micromamba config prepend channels conda-forge - - micromamba info - -install: - - micromamba env create -f environment.yml python=%PYTHON_VERSION% pywin32 - - micromamba activate mpl-dev - -test_script: - # Now build the thing.. - - set LINK=/LIBPATH:%cd%\lib - - pip install -v --no-build-isolation --editable .[dev] - # this should show no freetype dll... - - set "DUMPBIN=%VS140COMNTOOLS%\..\..\VC\bin\dumpbin.exe" - - '"%DUMPBIN%" /DEPENDENTS lib\matplotlib\ft2font*.pyd | findstr freetype.*.dll && exit /b 1 || exit /b 0' - - # this are optional dependencies so that we don't skip so many tests... - - if x%TEST_ALL% == xyes micromamba install -q ffmpeg inkscape - # miktex is available on conda, but seems to fail with permission errors. - # missing packages on conda-forge for imagemagick - # This install sometimes failed randomly :-( - # - choco install imagemagick - - # Test import of tkagg backend - - python -c - "import matplotlib as m; m.use('tkagg'); - import matplotlib.pyplot as plt; - print(plt.get_backend())" - # tests - - echo The following args are passed to pytest %PYTEST_ARGS% - - pytest %PYTEST_ARGS% - -artifacts: - - path: result_images\* - name: result_images - type: Zip - -on_finish: - - micromamba install codecov - - codecov -e PYTHON_VERSION PLATFORM -n "%PYTHON_VERSION% Windows" - -on_failure: - # Generate a html for visual tests - - python tools/visualize_tests.py --no-browser - - echo zipping images after a failure... - - 7z a result_images.zip result_images\ | grep -v "Compressing" - - appveyor PushArtifact result_images.zip - -matrix: - fast_finish: true diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 40ba933cf0d9..000000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,262 +0,0 @@ -# Circle CI configuration file -# https://circleci.com/docs/ ---- -version: 2.1 - - -####################################### -# Define some common steps as commands. -# - -commands: - check-skip: - steps: - - run: - name: Check-skip - command: | - export git_log=$(git log --max-count=1 --pretty=format:"%B" | tr "\n" " ") - echo "Got commit message:" - echo "${git_log}" - if [[ -v CIRCLE_PULL_REQUEST ]] && - [[ $git_log =~ (\[skip circle\]|\[circle skip\]|\[skip doc\]|\[doc skip\]) ]]; then - echo "Skip detected, exiting job ${CIRCLE_JOB} for PR ${CIRCLE_PULL_REQUEST}." - circleci-agent step halt; - fi - - merge: - steps: - - run: - name: Merge with upstream - command: | - if ! git remote -v | grep upstream; then - git remote add upstream https://github.com/matplotlib/matplotlib.git - fi - git fetch upstream - if [[ "$CIRCLE_BRANCH" != "main" ]] && \ - [[ "$CIRCLE_PR_NUMBER" != "" ]]; then - echo "Merging ${CIRCLE_PR_NUMBER}" - git pull --ff-only upstream "refs/pull/${CIRCLE_PR_NUMBER}/merge" - fi - - apt-install: - steps: - - run: - name: Install apt packages - command: | - sudo apt-get -qq update - sudo apt-get install -yy --no-install-recommends \ - cm-super \ - dvipng \ - ffmpeg \ - fonts-crosextra-carlito \ - fonts-freefont-otf \ - fonts-noto-cjk \ - fonts-wqy-zenhei \ - graphviz \ - inkscape \ - lmodern \ - ninja-build \ - optipng \ - texlive-fonts-recommended \ - texlive-latex-base \ - texlive-latex-extra \ - texlive-latex-recommended \ - texlive-pictures \ - texlive-xetex - - fonts-install: - steps: - - restore_cache: - key: fonts-4 - - run: - name: Install custom fonts - command: | - mkdir -p ~/.local/share/fonts - wget -nc \ - https://github.com/google/fonts/blob/master/ofl/felipa/Felipa-Regular.ttf?raw=true \ - -O ~/.local/share/fonts/Felipa-Regular.ttf || true - wget -nc \ - https://github.com/ipython/xkcd-font/blob/master/xkcd-script/font/xkcd-script.ttf?raw=true \ - -O ~/.local/share/fonts/xkcd-Script.ttf || true - fc-cache -f -v - - save_cache: - key: fonts-4 - paths: - - ~/.local/share/fonts/ - - pip-install: - description: Upgrade pip and setuptools and wheel to get as clean an install as possible - steps: - - run: - name: Upgrade pip, setuptools, wheel - command: | - python -m pip install --upgrade --user pip - python -m pip install --upgrade --user wheel - python -m pip install --upgrade --user 'setuptools!=60.6.0' - - doc-deps-install: - parameters: - numpy_version: - type: string - default: "" - steps: - - run: - name: Install Python dependencies - command: | - python -m pip install --user -r requirements/dev/build-requirements.txt - python -m pip install --user \ - numpy<< parameters.numpy_version >> \ - -r requirements/doc/doc-requirements.txt - python -m pip install --no-deps --user \ - git+https://github.com/matplotlib/mpl-sphinx-theme.git - - mpl-install: - steps: - - run: - name: Install Matplotlib - command: | - if [[ "$CIRCLE_BRANCH" == v*-doc ]]; then - # The v*-doc branches must build against the specified release. - version=${CIRCLE_BRANCH%-doc} - version=${version#v} - python -m pip install matplotlib==${version} - else - python -m pip install --user --verbose \ - --no-build-isolation --editable .[dev] - fi - - save_cache: - key: build-deps-2 - paths: - - subprojects/packagecache - - doc-build: - steps: - - restore_cache: - keys: - - sphinx-env-v1-{{ .BuildNum }}-{{ .Environment.CIRCLE_JOB }} - - sphinx-env-v1-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}-{{ .Environment.CIRCLE_JOB }} - - run: - name: Build documentation - command: | - # Set epoch to date of latest tag. - export SOURCE_DATE_EPOCH="$(git log -1 --format=%at $(git describe --abbrev=0))" - # Set release mode only when deploying to devdocs. - if [ "$CIRCLE_PROJECT_USERNAME" = "matplotlib" ] && \ - [ "$CIRCLE_BRANCH" = "main" ] && \ - [ "$CIRCLE_PR_NUMBER" = "" ]; then - export RELEASE_TAG='-t release' - fi - mkdir -p logs - make html O="-T $RELEASE_TAG -j4 -w /tmp/sphinxerrorswarnings.log" - rm -r build/html/_sources - working_directory: doc - - save_cache: - key: sphinx-env-v1-{{ .BuildNum }}-{{ .Environment.CIRCLE_JOB }} - paths: - - doc/build/doctrees - - doc-show-errors-warnings: - steps: - - run: - name: Extract possible build errors and warnings - command: | - (grep "WARNING\|ERROR" /tmp/sphinxerrorswarnings.log || - echo "No errors or warnings") - # Save logs as an artifact, and convert from absolute paths to - # repository-relative paths. - sed "s~$PWD/~~" /tmp/sphinxerrorswarnings.log > \ - doc/logs/sphinx-errors-warnings.log - when: always - - store_artifacts: - path: doc/logs/sphinx-errors-warnings.log - - doc-show-deprecations: - steps: - - run: - name: Extract possible deprecation warnings in examples and tutorials - command: | - (grep -rl DeprecationWarning doc/build/html/gallery || - echo "No deprecation warnings in gallery") - (grep -rl DeprecationWarning doc/build/html/plot_types || - echo "No deprecation warnings in plot_types") - (grep -rl DeprecationWarning doc/build/html/tutorials || - echo "No deprecation warnings in tutorials") - # Save deprecations that are from this absolute directory, and - # convert to repository-relative paths. - (grep -Ero --no-filename "$PWD/.+DeprecationWarning.+$" \ - doc/build/html/{gallery,plot_types,tutorials} || echo) | \ - sed "s~$PWD/~~" > doc/logs/sphinx-deprecations.log - when: always - - store_artifacts: - path: doc/logs/sphinx-deprecations.log - - doc-bundle: - steps: - - run: - name: Bundle sphinx-gallery documentation artifacts - command: > - tar cf doc/build/sphinx-gallery-files.tar.gz - doc/api/_as_gen - doc/gallery - doc/plot_types - doc/tutorials - when: always - - store_artifacts: - path: doc/build/sphinx-gallery-files.tar.gz - - deploy-docs: - steps: - - run: - name: "Deploy new docs" - command: ./.circleci/deploy-docs.sh - - -########################################## -# Here is where the real jobs are defined. -# - -jobs: - docs-python3: - docker: - - image: cimg/python:3.12 - resource_class: large - steps: - - checkout - - check-skip - - merge - - - apt-install - - fonts-install - - pip-install - - - doc-deps-install - - mpl-install - - - doc-build - - doc-show-errors-warnings - - doc-show-deprecations - - - doc-bundle - - - store_artifacts: - path: doc/build/html - - store_test_results: - path: doc/build/test-results - - - add_ssh_keys: - fingerprints: - - "be:c3:c1:d8:fb:a1:0e:37:71:72:d7:a3:40:13:8f:14" - - - deploy-docs - -######################################### -# Defining workflows gets us parallelism. -# - -workflows: - version: 2 - build: - jobs: - # NOTE: If you rename this job, then you must update the `if` condition - # and `circleci-jobs` option in `.github/workflows/circleci.yml`. - - docs-python3 diff --git a/.circleci/deploy-docs.sh b/.circleci/deploy-docs.sh deleted file mode 100755 index 8801d5fd073e..000000000000 --- a/.circleci/deploy-docs.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -set -e - -if [ "$CIRCLE_PROJECT_USERNAME" != "matplotlib" ] || \ - [ "$CIRCLE_BRANCH" != "main" ] || \ - [[ "$CIRCLE_PULL_REQUEST" == https://github.com/matplotlib/matplotlib/pull/* ]]; then - echo "Not uploading docs for ${CIRCLE_SHA1}"\ - "from non-main branch (${CIRCLE_BRANCH})"\ - "or pull request (${CIRCLE_PULL_REQUEST})"\ - "or non-Matplotlib org (${CIRCLE_PROJECT_USERNAME})." - exit -fi - -git clone git@github.com:matplotlib/devdocs.git - -cd devdocs - -git checkout --orphan gh-pages || true -git reset --hard first_commit - -git rm -rf . -cp -R ../doc/build/html/. . -touch .nojekyll - -git config user.email "MatplotlibCircleBot@nomail" -git config user.name "MatplotlibCircleBot" -git config push.default simple - -git add . -git commit -m "Docs build of $CIRCLE_SHA1" - -git push --set-upstream origin gh-pages --force diff --git a/.circleci/fetch_doc_logs.py b/.circleci/fetch_doc_logs.py deleted file mode 100644 index 0a5552a7721c..000000000000 --- a/.circleci/fetch_doc_logs.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -Download artifacts from CircleCI for a documentation build. - -This is run by the :file:`.github/workflows/circleci.yml` workflow in order to -get the warning/deprecation logs that will be posted on commits as checks. Logs -are downloaded from the :file:`docs/logs` artifact path and placed in the -:file:`logs` directory. - -Additionally, the artifact count for a build is produced as a workflow output, -by appending to the file specified by :env:`GITHUB_OUTPUT`. - -If there are no logs, an "ERROR" message is printed, but this is not fatal, as -the initial 'status' workflow runs when the build has first started, and there -are naturally no artifacts at that point. - -This script should be run by passing the CircleCI build URL as its first -argument. In the GitHub Actions workflow, this URL comes from -``github.event.target_url``. -""" -import json -import os -from pathlib import Path -import sys -from urllib.parse import urlparse -from urllib.request import URLError, urlopen - - -if len(sys.argv) != 2: - print('USAGE: fetch_doc_results.py CircleCI-build-url') - sys.exit(1) - -target_url = urlparse(sys.argv[1]) -*_, organization, repository, build_id = target_url.path.split('/') -print(f'Fetching artifacts from {organization}/{repository} for {build_id}') - -artifact_url = ( - f'https://circleci.com/api/v2/project/gh/' - f'{organization}/{repository}/{build_id}/artifacts' -) -print(artifact_url) -try: - with urlopen(artifact_url) as response: - artifacts = json.load(response) -except URLError: - artifacts = {'items': []} -artifact_count = len(artifacts['items']) -print(f'Found {artifact_count} artifacts') - -with open(os.environ['GITHUB_OUTPUT'], 'w+') as fd: - fd.write(f'count={artifact_count}\n') - -logs = Path('logs') -logs.mkdir(exist_ok=True) - -found = False -for item in artifacts['items']: - path = item['path'] - if path.startswith('doc/logs/'): - path = Path(path).name - print(f'Downloading {path} from {item["url"]}') - with urlopen(item['url']) as response: - (logs / path).write_bytes(response.read()) - found = True - -if not found: - print('ERROR: Did not find any artifact logs!') diff --git a/.github/workflows/circleci.yml b/.github/workflows/circleci.yml deleted file mode 100644 index d61db3f14345..000000000000 --- a/.github/workflows/circleci.yml +++ /dev/null @@ -1,75 +0,0 @@ ---- -name: "CircleCI artifact handling" -on: [status] -jobs: - circleci_artifacts_redirector_job: - if: "${{ github.event.context == 'ci/circleci: docs-python3' }}" - permissions: - statuses: write - runs-on: ubuntu-latest - name: Run CircleCI artifacts redirector - steps: - - name: GitHub Action step - uses: - scientific-python/circleci-artifacts-redirector-action@7eafdb60666f57706a5525a2f5eb76224dc8779b # v1.1.0 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - api-token: ${{ secrets.CIRCLECI_TOKEN }} - artifact-path: 0/doc/build/html/index.html - circleci-jobs: docs-python3 - job-title: View the built docs - - post_warnings_as_review: - if: "${{ github.event.context == 'ci/circleci: docs-python3' }}" - permissions: - contents: read - checks: write - pull-requests: write - runs-on: ubuntu-latest - name: Post warnings/errors as review - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - persist-credentials: false - - - name: Fetch result artifacts - id: fetch-artifacts - env: - target_url: "${{ github.event.target_url }}" - run: | - python .circleci/fetch_doc_logs.py "${target_url}" - - - name: Set up reviewdog - if: "${{ steps.fetch-artifacts.outputs.count != 0 }}" - uses: reviewdog/action-setup@e04ffabe3898a0af8d0fb1af00c188831c4b5893 # v1.3.2 - with: - reviewdog_version: latest - - - name: Post review - if: "${{ steps.fetch-artifacts.outputs.count != 0 }}" - env: - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - REVIEWDOG_SKIP_DOGHOUSE: "true" - CI_COMMIT: ${{ github.event.sha }} - CI_REPO_OWNER: ${{ github.event.repository.owner.login }} - CI_REPO_NAME: ${{ github.event.repository.name }} - run: | - # The 'status' event does not contain information in the way that - # reviewdog expects, so we unset those so it reads from the - # environment variables we set above. - unset GITHUB_ACTIONS GITHUB_EVENT_PATH - cat logs/sphinx-errors-warnings.log | \ - reviewdog \ - -efm '%f\:%l: %tEBUG: %m' \ - -efm '%f\:%l: %tNFO: %m' \ - -efm '%f\:%l: %tARNING: %m' \ - -efm '%f\:%l: %tRROR: %m' \ - -efm '%f\:%l: %tEVERE: %m' \ - -efm '%f\:%s: %tARNING: %m' \ - -efm '%f\:%s: %tRROR: %m' \ - -name=sphinx -tee -fail-on-error=false \ - -reporter=github-check -filter-mode=nofilter - cat logs/sphinx-deprecations.log | \ - reviewdog \ - -efm '%f\:%l: %m' \ - -name=examples -tee -reporter=github-check -filter-mode=nofilter diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index d6d1eba02560..000000000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,46 +0,0 @@ ---- -name: "CodeQL" - -on: - push: - branches: [main, v*.x] - pull_request: - # The branches below must be a subset of the branches above - branches: [main] - schedule: - - cron: '45 19 * * 1' - -jobs: - analyze: - if: github.repository == 'matplotlib/matplotlib' - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: ['c-cpp', 'javascript', 'python'] - - steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - persist-credentials: false - - - name: Initialize CodeQL - uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 - with: - languages: ${{ matrix.language }} - - - name: Build compiled code - if: matrix.language == 'c-cpp' - run: | - pip install --user --upgrade pip - pip install --user -v . - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 diff --git a/.github/workflows/mypy-stubtest.yml b/.github/workflows/mypy-stubtest.yml deleted file mode 100644 index 92a67236fb9d..000000000000 --- a/.github/workflows/mypy-stubtest.yml +++ /dev/null @@ -1,47 +0,0 @@ ---- -name: Mypy Stubtest -on: [pull_request] - -permissions: - contents: read - -jobs: - mypy-stubtest: - name: mypy-stubtest - runs-on: ubuntu-latest - permissions: - checks: write - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - persist-credentials: false - - - name: Set up Python 3 - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 - with: - python-version: '3.11' - - - name: Set up reviewdog - uses: reviewdog/action-setup@e04ffabe3898a0af8d0fb1af00c188831c4b5893 # v1.3.9 - - - name: Install tox - run: python -m pip install tox - - - name: Run mypy stubtest - env: - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -o pipefail - tox -e stubtest | \ - sed -e "s!.tox/stubtest/lib/python3.11/site-packages!lib!g" | \ - reviewdog \ - -efm '%Eerror: %m' \ - -efm '%CStub: in file %f:%l' \ - -efm '%CStub: in file %f' \ - -efm '%+CRuntime:%.%#' \ - -efm '%+CMISSING' \ - -efm '%+Cdef %.%#' \ - -efm '%+C<%.%#>' \ - -efm '%Z' \ - -reporter=github-check -tee -name=mypy-stubtest \ - -filter-mode=nofilter diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 85ace93445b6..a36d6c154eb8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -42,52 +42,16 @@ jobs: contents: read name: "Python ${{ matrix.python-version }} on ${{ matrix.os }} ${{ matrix.name-suffix }}" runs-on: ${{ matrix.os }} + continue-on-error: ${{ contains(matrix.name-suffix, 'pre-release') }} strategy: fail-fast: false matrix: include: - - name-suffix: "(Minimum Versions)" - os: ubuntu-22.04 - python-version: '3.11' - extra-requirements: '-c requirements/testing/minver.txt' - delete-font-cache: true - # https://github.com/matplotlib/matplotlib/issues/29844 - pygobject-ver: '<3.52.0' - - os: ubuntu-22.04 - python-version: '3.11' - CFLAGS: "-fno-lto" # Ensure that disabling LTO works. - extra-requirements: '-r requirements/testing/extra.txt' - # https://github.com/matplotlib/matplotlib/issues/29844 - pygobject-ver: '<3.52.0' - - os: ubuntu-22.04-arm - python-version: '3.12' - # https://github.com/matplotlib/matplotlib/issues/29844 - pygobject-ver: '<3.52.0' - - name-suffix: "(Extra TeX packages)" - os: ubuntu-22.04 - python-version: '3.13' - extra-packages: 'texlive-fonts-extra texlive-lang-cyrillic' - # https://github.com/matplotlib/matplotlib/issues/29844 - pygobject-ver: '<3.52.0' - - name-suffix: "Free-threaded" - os: ubuntu-22.04 - python-version: '3.13t' - # https://github.com/matplotlib/matplotlib/issues/29844 - pygobject-ver: '<3.52.0' - - os: ubuntu-24.04 - python-version: '3.12' - - os: macos-13 # This runner is on Intel chips. - # merge numpy and pandas install in nighties test when this runner is dropped - python-version: '3.11' - - os: macos-14 # This runner is on M1 (arm64) chips. - python-version: '3.12' - # https://github.com/matplotlib/matplotlib/issues/29732 - pygobject-ver: '<3.52.0' - - os: macos-14 # This runner is on M1 (arm64) chips. - python-version: '3.13' - # https://github.com/matplotlib/matplotlib/issues/29732 - pygobject-ver: '<3.52.0' + - name-suffix: "(Python pre-release)" + os: ubuntu-24.04 + python-version: '3.14-dev' + skip-interactive-backend-builds: true steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -226,6 +190,12 @@ jobs: PRE="--pre" fi + # On pre-release builds, use nightly wheels + if ${{ contains(matrix.name-suffix, 'pre-release') }}; then + PRE="--pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple" + PRE+=" --prefer-binary" + fi + # Install dependencies from PyPI. # Preinstall build requirements to enable no-build-isolation builds. python -m pip install --upgrade $PRE \ @@ -239,7 +209,7 @@ jobs: # Sphinx is needed to run sphinxext tests python -m pip install --upgrade sphinx!=6.1.2 - if [[ "${{ matrix.python-version }}" != '3.13t' ]]; then + if [[ "${{ matrix.skip-interactive-backend-builds }}" != "true" ]]; then # GUI toolkits are pip-installable only for some versions of Python # so don't fail if we can't install them. Make it easier to check # whether the install was successful by trying to import the toolkit @@ -265,8 +235,10 @@ jobs: # Even though PySide2 wheels can be installed on Python 3.12+, they are broken and since PySide2 is # deprecated, they are unlikely to be fixed. For the same deprecation reason, there are no wheels # on M1 macOS, so don't bother there either. - if [[ "${{ matrix.os }}" != 'macos-14' - && "${{ matrix.python-version }}" != '3.12' && "${{ matrix.python-version }}" != '3.13' ]]; then + version_atmost() { + printf "%s\n" "$1" "$2" | sort --version-sort --check=silent + } + if [[ "${{ matrix.os }}" != 'macos-14' ]] && version_atmost "${{ matrix.python-version }}" 3.11.999; then python -mpip install --upgrade pyside2 && python -c 'import PySide2.QtCore' && echo 'PySide2 is available' || @@ -288,7 +260,7 @@ jobs: echo 'wxPython is available' || echo 'wxPython is not available' - fi # Skip backends on Python 3.13t. + fi # skip-interactive-backend-builds - name: Install the nightly dependencies # Only install the nightly dependencies during the scheduled event @@ -333,13 +305,27 @@ jobs: if: matrix.delete-font-cache - name: Run pytest + timeout-minutes: 90 run: | if [[ "${{ matrix.python-version }}" == '3.13t' ]]; then export PYTHON_GIL=0 fi - pytest -rfEsXR -n auto \ - --maxfail=50 --timeout=300 --durations=25 \ - --cov-report=xml --cov=lib --log-level=DEBUG --color=yes + if "${{ contains(matrix.name-suffix, 'pre-release') }}"; then + FLAGS=(-k 'not test_backends_interactive') + else + FLAGS=() + fi + FLAGS+=( + --maxfail=50 + --timeout=300 + --durations=25 + --cov-report=xml + --cov=lib + --log-level=DEBUG + --color=yes + ) + pytest -rfEsXR -n auto -m 'not subprocess' "${FLAGS[@]}" + pytest -rfEsXR -m subprocess "${FLAGS[@]}" - name: Cleanup non-failed image files if: failure() diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index d68a9d36f0d3..000000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,162 +0,0 @@ -# Python package -# Create and test a Python package on multiple Python versions. -# Add steps that analyze code, save the dist with the build record, publish to a PyPI-compatible index, and -# more: -# https://docs.microsoft.com/en-us/azure/devops/pipelines/ecosystems/python?view=azure-devops - ---- -trigger: - branches: - exclude: - - v*-doc -pr: - branches: - exclude: - - v*-doc - paths: - exclude: - - doc/**/* - - galleries/**/* - -stages: - - - stage: Check - jobs: - - job: Skip - pool: - vmImage: 'ubuntu-latest' - variables: - DECODE_PERCENTS: 'false' - RET: 'true' - steps: - - bash: | - git_log=`git log --max-count=1 --skip=1 --pretty=format:"%B" | tr "\n" " "` - echo "##vso[task.setvariable variable=log]$git_log" - - bash: echo "##vso[task.setvariable variable=RET]false" - condition: >- - or(contains(variables.log, '[skip azp]'), - contains(variables.log, '[azp skip]'), - contains(variables.log, '[skip ci]'), - contains(variables.log, '[ci skip]'), - contains(variables.log, '[ci doc]')) - - bash: echo "##vso[task.setvariable variable=start_main;isOutput=true]$RET" - name: result - - - stage: Main - condition: and(succeeded(), eq(dependencies.Check.outputs['Skip.result.start_main'], 'true')) - dependsOn: Check - jobs: - - job: Pytest - strategy: - matrix: - Windows_py311: - vmImage: 'windows-2022' # Keep one job pinned to the oldest image - python.version: '3.11' - Windows_py312: - vmImage: 'windows-latest' - python.version: '3.12' - Windows_py313: - vmImage: 'windows-latest' - python.version: '3.13' - maxParallel: 4 - pool: - vmImage: '$(vmImage)' - steps: - - task: UsePythonVersion@0 - inputs: - versionSpec: '$(python.version)' - architecture: 'x64' - displayName: 'Use Python $(python.version)' - - - bash: | - choco install ninja - displayName: 'Install dependencies' - - - bash: | - python -m pip install --upgrade pip - python -m pip install --upgrade -r requirements/dev/build-requirements.txt - python -m pip install -r requirements/testing/all.txt -r requirements/testing/extra.txt - displayName: 'Install dependencies with pip' - - - bash: | - CONFIG='--config-settings=setup-args=--vsenv' - CONFIG="$CONFIG --config-settings=setup-args=-Dcpp_link_args=-PROFILE" - CONFIG="$CONFIG --config-settings=setup-args=-Dbuildtype=debug" - - python -m pip install \ - --no-build-isolation $CONFIG \ - --verbose --editable .[dev] - displayName: "Install self" - - - script: env - displayName: 'print env' - - - script: pip list - displayName: 'print pip' - - - bash: | - set -e - SESSION_ID=$(python -c "import uuid; print(uuid.uuid4(), end='')") - echo "Coverage session ID: ${SESSION_ID}" - VS=$(ls -d /c/Program\ Files*/Microsoft\ Visual\ Studio/*/Enterprise) - echo "Visual Studio: ${VS}" - DIR="$VS/Common7/IDE/Extensions/Microsoft/CodeCoverage.Console" - # This is for MSVC 2022 (on windows-latest). - TOOL="$DIR/Microsoft.CodeCoverage.Console.exe" - for f in build/cp*/src/*.pyd; do - echo $f - echo "==============================" - "$TOOL" instrument $f --session-id $SESSION_ID \ - --log-level Verbose --log-file instrument.log - cat instrument.log - rm instrument.log - done - echo "Starting $TOOL in server mode" - "$TOOL" collect \ - --session-id $SESSION_ID --server-mode \ - --output-format cobertura --output extensions.xml \ - --log-level Verbose --log-file extensions.log & - VS_VER=2022 - - echo "##vso[task.setvariable variable=VS_COVERAGE_TOOL]$TOOL" - - PYTHONFAULTHANDLER=1 pytest -rfEsXR -n 2 \ - --maxfail=50 --timeout=300 --durations=25 \ - --junitxml=junit/test-results.xml --cov-report=xml --cov=lib - - if [[ $VS_VER == 2022 ]]; then - "$TOOL" shutdown $SESSION_ID - echo "Coverage collection log" - echo "=======================" - cat extensions.log - else - "$TOOL" shutdown -session:$SESSION_ID - fi - displayName: 'pytest' - - - bash: | - if [[ -f extensions.coverage ]]; then - # For MSVC 2019. - "$VS_COVERAGE_TOOL" analyze -output:extensions.xml \ - -include_skipped_functions -include_skipped_modules \ - extensions.coverage - rm extensions.coverage - fi - displayName: 'Filter C coverage' - condition: succeededOrFailed() - - bash: | - bash <(curl -s https://codecov.io/bash) \ - -n "$PYTHON_VERSION $AGENT_OS" \ - -f 'coverage.xml' -f 'extensions.xml' - displayName: 'Upload to codecov.io' - condition: succeededOrFailed() - - - task: PublishTestResults@2 - inputs: - testResultsFiles: '**/test-results.xml' - testRunTitle: 'Python $(python.version)' - condition: succeededOrFailed() - - - publish: $(System.DefaultWorkingDirectory)/result_images - artifact: $(Agent.JobName)-result_images - condition: failed() diff --git a/lib/matplotlib/testing/conftest.py b/lib/matplotlib/testing/conftest.py index 2961e7f02f3f..4efc10766be7 100644 --- a/lib/matplotlib/testing/conftest.py +++ b/lib/matplotlib/testing/conftest.py @@ -15,6 +15,7 @@ def pytest_configure(config): ("markers", "backend: Set alternate Matplotlib backend temporarily."), ("markers", "baseline_images: Compare output against references."), ("markers", "pytz: Tests that require pytz to be installed."), + ("markers", "subprocess: Tests that start a subprocess."), ("filterwarnings", "error"), ("filterwarnings", "ignore:.*The py23 module has been deprecated:DeprecationWarning"), diff --git a/lib/matplotlib/tests/test_backend_inline.py b/lib/matplotlib/tests/test_backend_inline.py index 997e1e7186b1..e8b7cfbaa321 100644 --- a/lib/matplotlib/tests/test_backend_inline.py +++ b/lib/matplotlib/tests/test_backend_inline.py @@ -12,6 +12,7 @@ pytest.importorskip('matplotlib_inline') +@pytest.mark.subprocess def test_ipynb(): nb_path = Path(__file__).parent / 'data/test_inline_01.ipynb' diff --git a/lib/matplotlib/tests/test_backend_nbagg.py b/lib/matplotlib/tests/test_backend_nbagg.py index ccf74df20aab..da90934f6bc4 100644 --- a/lib/matplotlib/tests/test_backend_nbagg.py +++ b/lib/matplotlib/tests/test_backend_nbagg.py @@ -13,6 +13,7 @@ # From https://blog.thedataincubator.com/2016/06/testing-jupyter-notebooks/ +@pytest.mark.subprocess def test_ipynb(): nb_path = Path(__file__).parent / 'data/test_nbagg_01.ipynb' diff --git a/lib/matplotlib/tests/test_backend_webagg.py b/lib/matplotlib/tests/test_backend_webagg.py index 1d6769494ef9..e09d85ce2e26 100644 --- a/lib/matplotlib/tests/test_backend_webagg.py +++ b/lib/matplotlib/tests/test_backend_webagg.py @@ -6,6 +6,7 @@ from matplotlib.testing import subprocess_run_for_testing +@pytest.mark.subprocess @pytest.mark.parametrize("backend", ["webagg", "nbagg"]) def test_webagg_fallback(backend): pytest.importorskip("tornado") diff --git a/lib/matplotlib/tests/test_basic.py b/lib/matplotlib/tests/test_basic.py index f6aa1e458555..10e94ee3be9d 100644 --- a/lib/matplotlib/tests/test_basic.py +++ b/lib/matplotlib/tests/test_basic.py @@ -5,6 +5,8 @@ from matplotlib.testing import subprocess_run_for_testing +import pytest + def test_simple(): assert 1 + 1 == 2 @@ -28,6 +30,7 @@ def test_override_builtins(): assert overridden <= ok_to_override +@pytest.mark.subprocess def test_lazy_imports(): source = textwrap.dedent(""" import sys diff --git a/lib/matplotlib/tests/test_determinism.py b/lib/matplotlib/tests/test_determinism.py index 2ecc40dbd3c0..e14206437f86 100644 --- a/lib/matplotlib/tests/test_determinism.py +++ b/lib/matplotlib/tests/test_determinism.py @@ -139,6 +139,7 @@ def draw(self, renderer=None): fig.savefig(stdout, format=fmt) +@pytest.mark.subprocess @pytest.mark.parametrize( "objects, fmt, usetex", [ ("", "pdf", False), @@ -185,6 +186,7 @@ def test_determinism_check(objects, fmt, usetex): assert p == plots[0] +@pytest.mark.subprocess @pytest.mark.parametrize( "fmt, string", [ ("pdf", b"/CreationDate (D:20000101000000Z)"), diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index 97ee8672b1d4..3e9b95f4fd5a 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -288,6 +288,7 @@ def test_fontcache_thread_safe(): subprocess_run_helper(_test_threading, timeout=10) +@pytest.mark.subprocess def test_lockfilefailure(tmp_path): # The logic here: # 1. get a temp directory from pytest diff --git a/lib/matplotlib/tests/test_matplotlib.py b/lib/matplotlib/tests/test_matplotlib.py index d0a3f8c617e1..3a28ecb985a7 100644 --- a/lib/matplotlib/tests/test_matplotlib.py +++ b/lib/matplotlib/tests/test_matplotlib.py @@ -19,6 +19,7 @@ def test_parse_to_version_info(version_str, version_tuple): assert matplotlib._parse_to_version_info(version_str) == version_tuple +@pytest.mark.subprocess @pytest.mark.skipif(sys.platform == "win32", reason="chmod() doesn't work as is on Windows") @pytest.mark.skipif(sys.platform != "win32" and os.geteuid() == 0, @@ -37,6 +38,7 @@ def test_tmpconfigdir_warning(tmp_path): os.chmod(tmp_path, mode) +@pytest.mark.subprocess def test_importable_with_no_home(tmp_path): subprocess_run_for_testing( [sys.executable, "-c", diff --git a/lib/matplotlib/tests/test_preprocess_data.py b/lib/matplotlib/tests/test_preprocess_data.py index c983d78786e1..3825a0aecd09 100644 --- a/lib/matplotlib/tests/test_preprocess_data.py +++ b/lib/matplotlib/tests/test_preprocess_data.py @@ -245,6 +245,7 @@ def funcy(ax, x, y, z, t=None): funcy.__doc__) +@pytest.mark.subprocess def test_data_parameter_replacement(): """ Test that the docstring contains the correct *data* parameter stub diff --git a/lib/matplotlib/tests/test_pyplot.py b/lib/matplotlib/tests/test_pyplot.py index 55f7c33cb52e..336847f4b7ef 100644 --- a/lib/matplotlib/tests/test_pyplot.py +++ b/lib/matplotlib/tests/test_pyplot.py @@ -12,6 +12,7 @@ from matplotlib import pyplot as plt +@pytest.mark.subprocess def test_pyplot_up_to_date(tmp_path): pytest.importorskip("black") diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 2235f98b720f..ee9a6476703a 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -528,6 +528,7 @@ def test_rcparams_reset_after_fail(): assert mpl.rcParams['text.usetex'] is False +@pytest.mark.subprocess @pytest.mark.skipif(sys.platform != "linux", reason="Linux only") def test_backend_fallback_headless_invalid_backend(tmp_path): env = {**os.environ, @@ -545,6 +546,7 @@ def test_backend_fallback_headless_invalid_backend(tmp_path): env=env, check=True, stderr=subprocess.DEVNULL) +@pytest.mark.subprocess @pytest.mark.skipif(sys.platform != "linux", reason="Linux only") def test_backend_fallback_headless_auto_backend(tmp_path): # specify a headless mpl environment, but request a graphical (tk) backend @@ -567,6 +569,7 @@ def test_backend_fallback_headless_auto_backend(tmp_path): assert backend.strip().lower() == "agg" +@pytest.mark.subprocess @pytest.mark.skipif( sys.platform == "linux" and not _c_internal_utils.xdisplay_is_valid(), reason="headless") diff --git a/lib/matplotlib/tests/test_sphinxext.py b/lib/matplotlib/tests/test_sphinxext.py index ede3166a2e1b..5b6e2e6b6e70 100644 --- a/lib/matplotlib/tests/test_sphinxext.py +++ b/lib/matplotlib/tests/test_sphinxext.py @@ -16,6 +16,7 @@ tinypages = Path(__file__).parent / 'data/tinypages' +@pytest.mark.subprocess def build_sphinx_html(source_dir, doctree_dir, html_dir, extra_args=None): # Build the pages with warnings turned into errors extra_args = [] if extra_args is None else extra_args diff --git a/lib/matplotlib/tests/test_texmanager.py b/lib/matplotlib/tests/test_texmanager.py index 64dcbf46456d..d7adda36e615 100644 --- a/lib/matplotlib/tests/test_texmanager.py +++ b/lib/matplotlib/tests/test_texmanager.py @@ -63,6 +63,7 @@ def test_unicode_characters(): fig.canvas.draw() +@pytest.mark.subprocess @needs_usetex def test_openin_any_paranoid(): completed = subprocess_run_for_testing(