name: Tests on: workflow_dispatch: push: branches: - 'main' - '3.*' pull_request: branches: - 'main' - '3.*' permissions: contents: read concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}-reusable cancel-in-progress: true env: FORCE_COLOR: 1 jobs: build-context: name: Change detection # To use boolean outputs from this job, parse them as JSON. # Here's some examples: # # if: fromJSON(needs.build-context.outputs.run-docs) # # ${{ # fromJSON(needs.build-context.outputs.run-tests) # && 'truthy-branch' # || 'falsy-branch' # }} # uses: ./.github/workflows/reusable-context.yml check-docs: name: Docs needs: build-context if: fromJSON(needs.build-context.outputs.run-docs) uses: ./.github/workflows/reusable-docs.yml check-abi: name: 'Check if the ABI has changed' runs-on: ubuntu-22.04 # 24.04 causes spurious errors needs: build-context if: needs.build-context.outputs.run-tests == 'true' steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: actions/setup-python@v5 - name: Install dependencies run: | sudo ./.github/workflows/posix-deps-apt.sh sudo apt-get install -yq abigail-tools - name: Build CPython env: CFLAGS: -g3 -O0 run: | # Build Python with the libpython dynamic library ./configure --enable-shared make -j4 - name: Check for changes in the ABI id: check run: | if ! make check-abidump; then echo "Generated ABI file is not up to date." echo "Please add the release manager of this branch as a reviewer of this PR." echo "" echo "The up to date ABI file should be attached to this build as an artifact." echo "" echo "To learn more about this check: https://devguide.python.org/getting-started/setup-building/index.html#regenerate-the-abi-dump" echo "" exit 1 fi - name: Generate updated ABI files if: ${{ failure() && steps.check.conclusion == 'failure' }} run: | make regen-abidump - uses: actions/upload-artifact@v4 name: Publish updated ABI files if: ${{ failure() && steps.check.conclusion == 'failure' }} with: name: abi-data path: ./Doc/data/*.abi check-autoconf-regen: name: 'Check if Autoconf files are up to date' # Don't use ubuntu-latest but a specific version to make the job # reproducible: to get the same tools versions (autoconf, aclocal, ...) runs-on: ubuntu-24.04 container: image: ghcr.io/python/autoconf:2024.10.16.11360930377 timeout-minutes: 60 needs: build-context if: needs.build-context.outputs.run-tests == 'true' steps: - name: Install Git run: | apt update && apt install git -yq git config --global --add safe.directory "$GITHUB_WORKSPACE" - uses: actions/checkout@v4 with: fetch-depth: 1 persist-credentials: false - name: Check Autoconf and aclocal versions run: | grep "Generated by GNU Autoconf 2.71" configure grep "aclocal 1.16.5" aclocal.m4 grep -q "runstatedir" configure grep -q "PKG_PROG_PKG_CONFIG" aclocal.m4 - name: Regenerate autoconf files # Same command used by Tools/build/regen-configure.sh ($AUTORECONF) run: autoreconf -ivf -Werror - name: Check for changes run: | git add -u changes=$(git status --porcelain) # Check for changes in regenerated files if test -n "$changes"; then echo "Generated files not up to date." echo "Perhaps you forgot to run make regen-configure ;)" echo "configure files must be regenerated with a specific version of autoconf." echo "$changes" echo "" git diff --staged || true exit 1 fi check-generated-files: name: 'Check if generated files are up to date' # Don't use ubuntu-latest but a specific version to make the job # reproducible: to get the same tools versions (autoconf, aclocal, ...) runs-on: ubuntu-24.04 timeout-minutes: 60 needs: build-context if: needs.build-context.outputs.run-tests == 'true' steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: actions/setup-python@v5 with: python-version: '3.x' - name: Runner image version run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV" - name: Restore config.cache uses: actions/cache@v4 with: path: config.cache # Include env.pythonLocation in key to avoid changes in environment when setup-python updates Python key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.build-context.outputs.config-hash }}-${{ env.pythonLocation }} - name: Install dependencies run: sudo ./.github/workflows/posix-deps-apt.sh - name: Add ccache to PATH run: echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV" - name: Configure ccache action uses: hendrikmuhs/ccache-action@v1.2 with: save: false - name: Configure CPython run: | # Build Python with the libpython dynamic library ./configure --config-cache --with-pydebug --enable-shared - name: Build CPython run: | make -j4 regen-all make regen-stdlib-module-names regen-sbom - name: Check for changes run: | git add -u changes=$(git status --porcelain) # Check for changes in regenerated files if test -n "$changes"; then echo "Generated files not up to date." echo "Perhaps you forgot to run make regen-all or build.bat --regen. ;)" echo "configure files must be regenerated with a specific version of autoconf." echo "$changes" echo "" git diff --staged || true exit 1 fi - name: Check exported libpython symbols run: make smelly - name: Check limited ABI symbols run: make check-limited-abi - name: Check for unsupported C global variables if: github.event_name == 'pull_request' # $GITHUB_EVENT_NAME run: make check-c-globals build-windows: name: >- Windows ${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }} needs: build-context if: fromJSON(needs.build-context.outputs.run-windows-tests) strategy: fail-fast: false matrix: os: - windows-latest arch: - x64 free-threading: - false - true include: - os: windows-latest # FIXME(diegorusso): change to os: windows-aarch64 arch: arm64 free-threading: false - os: windows-latest # FIXME(diegorusso): change to os: windows-aarch64 arch: arm64 free-threading: true - os: windows-latest arch: Win32 free-threading: false uses: ./.github/workflows/reusable-windows.yml with: os: ${{ matrix.os }} arch: ${{ matrix.arch }} free-threading: ${{ matrix.free-threading }} build-windows-msi: name: >- # ${{ '' } is a hack to nest jobs under the same sidebar category Windows MSI${{ '' }} needs: build-context if: fromJSON(needs.build-context.outputs.run-windows-msi) strategy: fail-fast: false matrix: arch: - x86 - x64 - arm64 uses: ./.github/workflows/reusable-windows-msi.yml with: arch: ${{ matrix.arch }} build-macos: name: >- macOS ${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }} needs: build-context if: needs.build-context.outputs.run-tests == 'true' strategy: fail-fast: false matrix: # Cirrus and macos-14 are M1, macos-13 is default GHA Intel. # macOS 13 only runs tests against the GIL-enabled CPython. # Cirrus used for upstream, macos-14 for forks. os: - ghcr.io/cirruslabs/macos-runner:sonoma - macos-14 - macos-13 is-fork: # only used for the exclusion trick - ${{ github.repository_owner != 'python' }} free-threading: - false - true exclude: - os: ghcr.io/cirruslabs/macos-runner:sonoma is-fork: true - os: macos-14 is-fork: false - os: macos-13 free-threading: true uses: ./.github/workflows/reusable-macos.yml with: config_hash: ${{ needs.build-context.outputs.config-hash }} free-threading: ${{ matrix.free-threading }} os: ${{ matrix.os }} build-ubuntu: name: >- Ubuntu ${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }} needs: build-context if: needs.build-context.outputs.run-tests == 'true' strategy: fail-fast: false matrix: free-threading: - false - true uses: ./.github/workflows/reusable-ubuntu.yml with: config_hash: ${{ needs.build-context.outputs.config-hash }} free-threading: ${{ matrix.free-threading }} build-ubuntu-ssltests: name: 'Ubuntu SSL tests with OpenSSL' runs-on: ${{ matrix.os }} timeout-minutes: 60 needs: build-context if: needs.build-context.outputs.run-tests == 'true' strategy: fail-fast: false matrix: os: [ubuntu-24.04] openssl_ver: [3.0.15, 3.1.7, 3.2.3, 3.3.2] env: OPENSSL_VER: ${{ matrix.openssl_ver }} MULTISSL_DIR: ${{ github.workspace }}/multissl OPENSSL_DIR: ${{ github.workspace }}/multissl/openssl/${{ matrix.openssl_ver }} LD_LIBRARY_PATH: ${{ github.workspace }}/multissl/openssl/${{ matrix.openssl_ver }}/lib steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: Runner image version run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV" - name: Restore config.cache uses: actions/cache@v4 with: path: config.cache key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.build-context.outputs.config-hash }} - name: Register gcc problem matcher run: echo "::add-matcher::.github/problem-matchers/gcc.json" - name: Install dependencies run: sudo ./.github/workflows/posix-deps-apt.sh - name: Configure OpenSSL env vars run: | echo "MULTISSL_DIR=${GITHUB_WORKSPACE}/multissl" >> "$GITHUB_ENV" echo "OPENSSL_DIR=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}" >> "$GITHUB_ENV" echo "LD_LIBRARY_PATH=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}/lib" >> "$GITHUB_ENV" - name: 'Restore OpenSSL build' id: cache-openssl uses: actions/cache@v4 with: path: ./multissl/openssl/${{ env.OPENSSL_VER }} key: ${{ matrix.os }}-multissl-openssl-${{ env.OPENSSL_VER }} - name: Install OpenSSL if: steps.cache-openssl.outputs.cache-hit != 'true' run: python3 Tools/ssl/multissltests.py --steps=library --base-directory "$MULTISSL_DIR" --openssl "$OPENSSL_VER" --system Linux - name: Add ccache to PATH run: | echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV" - name: Configure ccache action uses: hendrikmuhs/ccache-action@v1.2 with: save: false - name: Configure CPython run: ./configure --config-cache --with-pydebug --with-openssl=$OPENSSL_DIR - name: Build CPython run: make -j4 - name: Display build info run: make pythoninfo - name: SSL tests run: ./python Lib/test/ssltests.py build-wasi: name: 'WASI' needs: build-context if: needs.build-context.outputs.run-tests == 'true' uses: ./.github/workflows/reusable-wasi.yml with: config_hash: ${{ needs.build-context.outputs.config-hash }} test-hypothesis: name: "Hypothesis tests on Ubuntu" runs-on: ubuntu-24.04 timeout-minutes: 60 needs: build-context if: needs.build-context.outputs.run-tests == 'true' env: OPENSSL_VER: 3.0.15 PYTHONSTRICTEXTENSIONBUILD: 1 steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: Register gcc problem matcher run: echo "::add-matcher::.github/problem-matchers/gcc.json" - name: Install dependencies run: sudo ./.github/workflows/posix-deps-apt.sh - name: Configure OpenSSL env vars run: | echo "MULTISSL_DIR=${GITHUB_WORKSPACE}/multissl" >> "$GITHUB_ENV" echo "OPENSSL_DIR=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}" >> "$GITHUB_ENV" echo "LD_LIBRARY_PATH=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}/lib" >> "$GITHUB_ENV" - name: 'Restore OpenSSL build' id: cache-openssl uses: actions/cache@v4 with: path: ./multissl/openssl/${{ env.OPENSSL_VER }} key: ${{ runner.os }}-multissl-openssl-${{ env.OPENSSL_VER }} - name: Install OpenSSL if: steps.cache-openssl.outputs.cache-hit != 'true' run: python3 Tools/ssl/multissltests.py --steps=library --base-directory $MULTISSL_DIR --openssl $OPENSSL_VER --system Linux - name: Add ccache to PATH run: | echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV" - name: Configure ccache action uses: hendrikmuhs/ccache-action@v1.2 with: save: false - name: Setup directory envs for out-of-tree builds run: | echo "CPYTHON_RO_SRCDIR=$(realpath -m "${GITHUB_WORKSPACE}"/../cpython-ro-srcdir)" >> "$GITHUB_ENV" echo "CPYTHON_BUILDDIR=$(realpath -m "${GITHUB_WORKSPACE}"/../cpython-builddir)" >> "$GITHUB_ENV" - name: Create directories for read-only out-of-tree builds run: mkdir -p "$CPYTHON_RO_SRCDIR" "$CPYTHON_BUILDDIR" - name: Bind mount sources read-only run: sudo mount --bind -o ro "$GITHUB_WORKSPACE" "$CPYTHON_RO_SRCDIR" - name: Runner image version run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV" - name: Restore config.cache uses: actions/cache@v4 with: path: ${{ env.CPYTHON_BUILDDIR }}/config.cache key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.build-context.outputs.config-hash }} - name: Configure CPython out-of-tree working-directory: ${{ env.CPYTHON_BUILDDIR }} run: | ../cpython-ro-srcdir/configure \ --config-cache \ --with-pydebug \ --with-openssl="$OPENSSL_DIR" - name: Build CPython out-of-tree working-directory: ${{ env.CPYTHON_BUILDDIR }} run: make -j4 - name: Display build info working-directory: ${{ env.CPYTHON_BUILDDIR }} run: make pythoninfo - name: Remount sources writable for tests # some tests write to srcdir, lack of pyc files slows down testing run: sudo mount "$CPYTHON_RO_SRCDIR" -oremount,rw - name: Setup directory envs for out-of-tree builds run: | echo "CPYTHON_BUILDDIR=$(realpath -m "${GITHUB_WORKSPACE}"/../cpython-builddir)" >> "$GITHUB_ENV" - name: "Create hypothesis venv" working-directory: ${{ env.CPYTHON_BUILDDIR }} run: | VENV_LOC=$(realpath -m .)/hypovenv VENV_PYTHON=$VENV_LOC/bin/python echo "HYPOVENV=${VENV_LOC}" >> "$GITHUB_ENV" echo "VENV_PYTHON=${VENV_PYTHON}" >> "$GITHUB_ENV" ./python -m venv "$VENV_LOC" && "$VENV_PYTHON" -m pip install -r "${GITHUB_WORKSPACE}/Tools/requirements-hypothesis.txt" - name: 'Restore Hypothesis database' id: cache-hypothesis-database uses: actions/cache@v4 with: path: ./hypothesis key: hypothesis-database-${{ github.head_ref || github.run_id }} restore-keys: | hypothesis-database- - name: "Run tests" working-directory: ${{ env.CPYTHON_BUILDDIR }} run: | # Most of the excluded tests are slow test suites with no property tests # # (GH-104097) test_sysconfig is skipped because it has tests that are # failing when executed from inside a virtual environment. "${VENV_PYTHON}" -m test \ -W \ -o \ -j4 \ -x test_asyncio \ -x test_multiprocessing_fork \ -x test_multiprocessing_forkserver \ -x test_multiprocessing_spawn \ -x test_concurrent_futures \ -x test_socket \ -x test_subprocess \ -x test_signal \ -x test_sysconfig - uses: actions/upload-artifact@v4 if: always() with: name: hypothesis-example-db path: .hypothesis/examples/ build-asan: name: 'Address sanitizer' runs-on: ${{ matrix.os }} timeout-minutes: 60 needs: build-context if: needs.build-context.outputs.run-tests == 'true' strategy: fail-fast: false matrix: os: [ubuntu-24.04] env: OPENSSL_VER: 3.0.15 PYTHONSTRICTEXTENSIONBUILD: 1 ASAN_OPTIONS: detect_leaks=0:allocator_may_return_null=1:handle_segv=0 steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: Runner image version run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV" - name: Restore config.cache uses: actions/cache@v4 with: path: config.cache key: ${{ github.job }}-${{ env.IMAGE_OS_VERSION }}-${{ needs.build-context.outputs.config-hash }} - name: Register gcc problem matcher run: echo "::add-matcher::.github/problem-matchers/gcc.json" - name: Install dependencies run: sudo ./.github/workflows/posix-deps-apt.sh - name: Set up GCC-10 for ASAN uses: egor-tensin/setup-gcc@v1 with: version: 10 - name: Configure OpenSSL env vars run: | echo "MULTISSL_DIR=${GITHUB_WORKSPACE}/multissl" >> "$GITHUB_ENV" echo "OPENSSL_DIR=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}" >> "$GITHUB_ENV" echo "LD_LIBRARY_PATH=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}/lib" >> "$GITHUB_ENV" - name: 'Restore OpenSSL build' id: cache-openssl uses: actions/cache@v4 with: path: ./multissl/openssl/${{ env.OPENSSL_VER }} key: ${{ matrix.os }}-multissl-openssl-${{ env.OPENSSL_VER }} - name: Install OpenSSL if: steps.cache-openssl.outputs.cache-hit != 'true' run: python3 Tools/ssl/multissltests.py --steps=library --base-directory "$MULTISSL_DIR" --openssl "$OPENSSL_VER" --system Linux - name: Add ccache to PATH run: | echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV" - name: Configure ccache action uses: hendrikmuhs/ccache-action@v1.2 with: save: ${{ github.event_name == 'push' }} max-size: "200M" - name: Configure CPython run: ./configure --config-cache --with-address-sanitizer --without-pymalloc - name: Build CPython run: make -j4 - name: Display build info run: make pythoninfo - name: Tests run: xvfb-run make test build-tsan: name: >- Thread sanitizer ${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }} needs: build-context if: needs.build-context.outputs.run-tests == 'true' strategy: fail-fast: false matrix: free-threading: - false - true uses: ./.github/workflows/reusable-tsan.yml with: config_hash: ${{ needs.build-context.outputs.config-hash }} free-threading: ${{ matrix.free-threading }} # CIFuzz job based on https://google.github.io/oss-fuzz/getting-started/continuous-integration/ cifuzz: name: CIFuzz runs-on: ubuntu-latest timeout-minutes: 60 needs: build-context if: needs.build-context.outputs.run-ci-fuzz == 'true' permissions: security-events: write strategy: fail-fast: false matrix: sanitizer: [address, undefined, memory] steps: - name: Build fuzzers (${{ matrix.sanitizer }}) id: build uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master with: oss-fuzz-project-name: cpython3 sanitizer: ${{ matrix.sanitizer }} - name: Run fuzzers (${{ matrix.sanitizer }}) uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master with: fuzz-seconds: 600 oss-fuzz-project-name: cpython3 output-sarif: true sanitizer: ${{ matrix.sanitizer }} - name: Upload crash if: failure() && steps.build.outcome == 'success' uses: actions/upload-artifact@v4 with: name: ${{ matrix.sanitizer }}-artifacts path: ./out/artifacts - name: Upload SARIF if: always() && steps.build.outcome == 'success' uses: github/codeql-action/upload-sarif@v3 with: sarif_file: cifuzz-sarif/results.sarif checkout_path: cifuzz-sarif all-required-green: # This job does nothing and is only used for the branch protection name: All required checks pass runs-on: ubuntu-latest timeout-minutes: 5 needs: - build-context # Transitive dependency, needed to access `run-tests` value - check-docs - check-autoconf-regen - check-generated-files - build-windows - build-windows-msi - build-macos - build-ubuntu - build-ubuntu-ssltests - build-wasi - test-hypothesis - build-asan - build-tsan - cifuzz if: always() steps: - name: Check whether the needed jobs succeeded or failed uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe with: allowed-failures: >- build-windows-msi, build-ubuntu-ssltests, test-hypothesis, cifuzz, allowed-skips: >- ${{ !fromJSON(needs.build-context.outputs.run-docs) && ' check-docs, ' || '' }} ${{ needs.build-context.outputs.run-tests != 'true' && ' check-autoconf-regen, check-generated-files, build-macos, build-ubuntu, build-ubuntu-ssltests, build-wasi, test-hypothesis, build-asan, build-tsan, ' || '' }} ${{ !fromJSON(needs.build-context.outputs.run-windows-tests) && ' build-windows, ' || '' }} ${{ !fromJSON(needs.build-context.outputs.run-ci-fuzz) && ' cifuzz, ' || '' }} jobs: ${{ toJSON(needs) }}