name: Tests on: workflow_dispatch: push: branches: - 'main' - '3.*' pull_request: branches: - 'main' - '3.*' permissions: contents: read concurrency: # https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#concurrency # 'group' must be a key uniquely representing a PR or push event. # github.workflow is the workflow name # github.actor is the user invoking the workflow # github.head_ref is the source branch of the PR or otherwise blank # github.run_id is a unique number for the current run group: ${{ github.workflow }}-${{ github.actor }}-${{ github.head_ref || github.run_id }} 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - name: Install dependencies run: | sudo ./.github/workflows/posix-deps-apt.sh sudo apt-get install -yq --no-install-recommends 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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: '3.x' - name: Runner image version run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV" - name: Install dependencies run: sudo ./.github/workflows/posix-deps-apt.sh - 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 - name: Check for undocumented C APIs run: make check-c-api-docs 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-2022 arch: - x64 free-threading: - false - true include: - os: windows-2022 # FIXME(diegorusso): change to os: windows-aarch64 arch: arm64 free-threading: false - os: windows-2022 # FIXME(diegorusso): change to os: windows-aarch64 arch: arm64 free-threading: true - os: windows-2022 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: # ${{ '' } is a hack to nest jobs under the same sidebar category. name: Windows MSI${{ '' }} # zizmor: ignore[obfuscation] 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-macos == 'true' strategy: fail-fast: false matrix: # macos-26 is Apple Silicon, macos-15-intel is Intel. # macos-15-intel only runs tests against the GIL-enabled CPython. os: - macos-26 - macos-15-intel free-threading: - false - true exclude: - os: macos-15-intel free-threading: true uses: ./.github/workflows/reusable-macos.yml with: 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-ubuntu == 'true' strategy: fail-fast: false matrix: free-threading: - false - true uses: ./.github/workflows/reusable-ubuntu.yml with: 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-ubuntu == 'true' strategy: fail-fast: false matrix: os: [ubuntu-24.04] # Keep 1.1.1w in our list despite it being upstream EOL and otherwise # unsupported as it most resembles other 1.1.1-work-a-like ssl APIs # supported by important vendors such as AWS-LC. openssl_ver: [1.1.1w, 3.0.20, 3.3.7, 3.4.5, 3.5.6, 3.6.2] # See Tools/ssl/make_ssl_data.py for notes on adding a new version 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Runner image version run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV" - 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@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 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: 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-android: name: Android (${{ matrix.arch }}) needs: build-context if: needs.build-context.outputs.run-android == 'true' timeout-minutes: 60 strategy: fail-fast: false matrix: include: - arch: aarch64 runs-on: macos-26 - arch: x86_64 runs-on: ubuntu-24.04 runs-on: ${{ matrix.runs-on }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Build and test run: ./Android/android.py ci --fast-ci ${{ matrix.arch }}-linux-android build-wasi: name: 'WASI' needs: build-context if: needs.build-context.outputs.run-wasi == 'true' uses: ./.github/workflows/reusable-wasi.yml test-hypothesis: name: "Hypothesis tests on Ubuntu" runs-on: ubuntu-24.04 timeout-minutes: 60 needs: build-context if: needs.build-context.outputs.run-ubuntu == 'true' env: OPENSSL_VER: 3.0.20 PYTHONSTRICTEXTENSIONBUILD: 1 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 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@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 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: 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: 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@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: ${{ env.CPYTHON_BUILDDIR }}/.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 \ --slowest \ -j4 \ --timeout 900 \ -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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 if: always() with: name: hypothesis-example-db path: ${{ env.CPYTHON_BUILDDIR }}/.hypothesis/examples/ build-asan: name: 'Address sanitizer' runs-on: ${{ matrix.os }} timeout-minutes: 60 needs: build-context if: needs.build-context.outputs.run-ubuntu == 'true' strategy: fail-fast: false matrix: os: [ubuntu-24.04] env: OPENSSL_VER: 3.0.20 PYTHONSTRICTEXTENSIONBUILD: 1 ASAN_OPTIONS: detect_leaks=0:allocator_may_return_null=1:handle_segv=0 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Runner image version run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV" - 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@a2861a8b8538f49cf2850980acccf6b05a1b2ae4 # v2.0 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@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 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: 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-san: # ${{ '' } is a hack to nest jobs under the same sidebar category. name: Sanitizers${{ '' }} # zizmor: ignore[obfuscation] needs: build-context if: needs.build-context.outputs.run-ubuntu == 'true' strategy: fail-fast: false matrix: check-name: - Thread free-threading: - false - true sanitizer: - TSan uses: ./.github/workflows/reusable-san.yml with: sanitizer: ${{ matrix.sanitizer }} free-threading: ${{ matrix.free-threading }} cifuzz: # ${{ '' } is a hack to nest jobs under the same sidebar category. name: CIFuzz${{ '' }} # zizmor: ignore[obfuscation] needs: build-context if: >- needs.build-context.outputs.run-ci-fuzz == 'true' || needs.build-context.outputs.run-ci-fuzz-stdlib == 'true' permissions: contents: read security-events: write strategy: fail-fast: false matrix: sanitizer: - address oss-fuzz-project-name: - cpython3 - python3-libraries include: - sanitizer: undefined oss-fuzz-project-name: cpython3 - sanitizer: memory oss-fuzz-project-name: cpython3 exclude: # Note that the 'no-exclude' sentinel below is to prevent # an empty string value from excluding all jobs and causing # GHA to create a 'default' matrix entry with all empty values. - oss-fuzz-project-name: >- ${{ needs.build-context.outputs.run-ci-fuzz == 'true' && 'no-exclude' || 'cpython3' }} - oss-fuzz-project-name: >- ${{ needs.build-context.outputs.run-ci-fuzz-stdlib == 'true' && 'no-exclude' || 'python3-libraries' }} uses: ./.github/workflows/reusable-cifuzz.yml with: oss-fuzz-project-name: ${{ matrix.oss-fuzz-project-name }} sanitizer: ${{ matrix.sanitizer }} 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-san - cifuzz if: always() steps: - name: Check whether the needed jobs succeeded or failed uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe with: allowed-failures: >- build-android, 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, ' || '' }} ${{ !fromJSON(needs.build-context.outputs.run-windows-tests) && 'build-windows,' || '' }} ${{ !fromJSON(needs.build-context.outputs.run-ci-fuzz) && !fromJSON(needs.build-context.outputs.run-ci-fuzz-stdlib) && 'cifuzz,' || '' }} ${{ !fromJSON(needs.build-context.outputs.run-macos) && 'build-macos,' || '' }} ${{ !fromJSON(needs.build-context.outputs.run-ubuntu) && ' build-ubuntu, build-ubuntu-ssltests, test-hypothesis, build-asan, build-san, ' || '' }} ${{ !fromJSON(needs.build-context.outputs.run-android) && 'build-android,' || '' }} ${{ !fromJSON(needs.build-context.outputs.run-wasi) && 'build-wasi,' || '' }} jobs: ${{ toJSON(needs) }}