name: Linux tests

# This file is meant for testing across supported Python versions, build types
# and interpreters (python-dbg, a pre-release Python in summer time),
# build-via-sdist, run benchmarks, measure code coverage, and other build
# options.

on:
  push:
    branches:
      # coverage comparison in the "full" step needs to run on main after merges
      - main
  pull_request:
    branches:
      - main
      - maintenance/**
    paths-ignore:
      - '**.pyi'
      - '**.md'
      - '**.rst'
      - 'tools/stubtest/**'

defaults:
  run:
    shell: bash

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

permissions:
  contents: read # to fetch code (actions/checkout)

jobs:
  lint:
    # To enable this job and subsequent jobs on a fork, comment out:
    if: github.repository == 'numpy/numpy' && github.event_name != 'push'
    runs-on: ubuntu-latest
    continue-on-error: true
    steps:
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-depth: 0
        persist-credentials: false
    - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      with:
        python-version: '3.12'
    - name: Install linter requirements
      run:
        python -m pip install -r requirements/linter_requirements.txt
    - name: Run linter on PR
      env:
        BASE_REF: ${{ github.base_ref }}
      run:
        spin lint
    - name: Check Python.h is first file included
      run: |
        python tools/check_python_h_first.py


  smoke_test:
    # To enable this job on a fork, comment out:
    if: github.repository == 'numpy/numpy'
    runs-on: ubuntu-latest
    env:
      MESON_ARGS: "-Dallow-noblas=true -Dcpu-baseline=none -Dcpu-dispatch=none"
    strategy:
      matrix:
        version: ["3.12", "3.14t"]
    steps:
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false
    - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      with:
        python-version: ${{ matrix.version }}
    - uses: ./.github/meson_actions

  debug:
    needs: [smoke_test]
    runs-on: ubuntu-24.04
    if: github.event_name != 'push'
    steps:
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false
    - name: Install debug Python
      uses: deadsnakes/action@e640ac8743173a67cca4d7d77cd837e514bf98e8 # v3.2.0
      with:
        python-version: '3.14'
        debug: true
    - name: Install dependencies
      run: |
        python --version
        pip install -U pip
        pip install -r requirements/build_requirements.txt
        pip install -r requirements/test_requirements.txt
    - name: Build NumPy debug
      run: |
        spin build -- -Dbuildtype=debug -Dallow-noblas=true
    - name: Run test suite
      run: |
        spin test -- --timeout=600 --durations=10

  all_versions:
    # like the smoke tests but runs on more Python versions
    needs: [smoke_test]
    # To enable this job on a fork, comment out:
    if: github.repository == 'numpy/numpy'
    runs-on: ubuntu-latest
    env:
      MESON_ARGS: "-Dallow-noblas=true -Dcpu-baseline=none -Dcpu-dispatch=none"
    strategy:
      matrix:
        version: ["3.13", "3.14", "3.15-dev", "3.15t-dev"]
    steps:
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false
    - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      with:
        python-version: ${{ matrix.version }}
    - uses: ./.github/meson_actions

  full:
    # Install as editable, then run the full test suite with code coverage
    needs: [smoke_test]
    runs-on: ubuntu-22.04
    steps:
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false
    - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      with:
        python-version: '3.12'
    - name: Install build and test dependencies from PyPI
      run: |
        pip install -r requirements/build_requirements.txt
        pip install -r requirements/test_requirements.txt
    - name: Install gfortran and setup OpenBLAS (MacPython build)
      run: |
        set -xe
        sudo apt update
        sudo apt install gfortran libgfortran5
        python -m pip install -r requirements/ci32_requirements.txt
        mkdir -p ./.openblas
        python -c"import scipy_openblas32 as ob32; print(ob32.get_pkg_config())" > ./.openblas/scipy-openblas.pc

    - name: Install as editable
      env:
        PKG_CONFIG_PATH: ${{ github.workspace }}/.openblas
        PYTHONSAFEPATH: 1  # regression check for gh-24907
      run: |
        pip install -e . --no-build-isolation
    - name: Run full test suite
      run: |
        pytest numpy --durations=10 --timeout=600 --cov-report=html:build/coverage
        # TODO: gcov
      env:
        PYTHONOPTIMIZE: 2
        PYTHONSAFEPATH: 1

  armhf_test:
    # Tests NumPy on 32-bit ARM hard-float (armhf) via compatibility mode
    # running on aarch64 (ARM 64-bit) GitHub runners.
    needs: [smoke_test]
    if: github.repository == 'numpy/numpy'
    runs-on: ubuntu-24.04-arm
    steps:
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false

    - name: Creates new container
      run: |
        docker run --name the_container --interactive \
          -v $(pwd):/numpy arm32v7/ubuntu:24.04 /bin/linux32 /bin/bash -c "
          apt update &&
          apt install -y ninja-build cmake git python3 python-is-python3 python3-dev python3-pip python3-venv
        "
        docker commit the_container the_container

    - name: Meson Build
      run: |
        docker run --rm -e "TERM=xterm-256color" \
          -v $(pwd):/numpy the_container \
          /bin/script -e -q -c "/bin/linux32 /bin/bash --noprofile --norc -eo pipefail -c '
            cd /numpy &&
            python -m venv venv &&
            source venv/bin/activate &&
            python -m pip install -r /numpy/requirements/build_requirements.txt &&
            python -m pip install -r /numpy/requirements/test_requirements.txt &&
            spin build
          '"

    - name: Meson Log
      if: always()
      run: 'cat build/meson-logs/meson-log.txt'

    - name: Run Tests
      run: |
        docker run --rm -e "TERM=xterm-256color" \
          -v $(pwd):/numpy the_container \
          /bin/script -e -q -c "/bin/linux32 /bin/bash --noprofile --norc -eo pipefail -c '
            cd /numpy && source venv/bin/activate && spin test -m full -- --timeout=600 --durations=10
          '"

  benchmark:
    needs: [smoke_test]
    runs-on: ubuntu-latest
    if: github.event_name != 'push'
    steps:
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false
    - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      with:
        python-version: '3.12'
    - name: Install build and benchmarking dependencies
      run: |
        sudo apt-get update
        sudo apt-get install libopenblas-dev ninja-build
        pip install "asv<0.6.5" virtualenv packaging -r requirements/build_requirements.txt
    - name: Install NumPy
      run: |
        spin build -- -Dcpu-dispatch=none
    # Ensure to keep the below steps as single-line bash commands (it's a
    # workaround for asv#1333, and it may have side-effects on multi-line commands)
    - name: Appease asv's need for machine info
      shell: 'script -q -e -c "bash --noprofile --norc -eo pipefail {0}"'
      run: |
        asv machine --yes --config benchmarks/asv.conf.json
    - name: Run benchmarks
      shell: 'script -q -e -c "bash --noprofile --norc -eo pipefail {0}"'
      run: |
        spin bench --quick
    # These are run on CircleCI
    # - name: Check docstests
    #   shell: 'script -q -e -c "bash --noprofile --norc -eo pipefail {0}"'
    #   run: |
    #     pip install -r requirements/doc_requirements.txt -r requirements/test_requirements.txt
    #     spin check-docs -v
    #     spin check-tutorials -v

  sdist:
    needs: [smoke_test]
    runs-on: ubuntu-latest
    if: github.event_name != 'push'
    steps:
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false
    - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      with:
        python-version: '3.12'
    - name: Install gfortran and setup OpenBLAS (sdist build)
      run: |
        set -xe
        python -m pip install -r requirements/ci_requirements.txt
        mkdir -p ./.openblas
        python -c"import scipy_openblas64 as ob64; print(ob64.get_pkg_config())" > ./.openblas/scipy-openblas.pc
    - name: Build a wheel via an sdist
      env:
        PKG_CONFIG_PATH: ${{ github.workspace }}/.openblas
      run: |
        pip install build
        python -m build
        pip install dist/numpy*.whl
    - name: Install test dependencies
      run: |
        pip install -r requirements/test_requirements.txt
    - name: Run test suite
      run: |
        cd tools
        pytest --pyargs numpy -m "not slow"
    - name: Test SWIG binding
      run: |
        sudo apt update
        sudo apt install make swig
        pip install setuptools
        make -C tools/swig/test test

  array_api_tests:
    needs: [smoke_test]
    runs-on: ubuntu-latest
    if: github.event_name != 'push'
    steps:
    - name: Checkout NumPy
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false
    - name: Checkout array-api-tests
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        repository: data-apis/array-api-tests
        ref: '41379d15d26d67a1e66c840e775d41a8a7fb1516'  # v2026.02.26
        submodules: 'true'
        path: 'array-api-tests'
        persist-credentials: false
    - name: Set up Python
      uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      with:
        python-version: '3.12'
    - name: Install build and test dependencies from PyPI
      run: |
        python -m pip install -r requirements/build_requirements.txt
        python -m pip install -r requirements/test_requirements.txt
        python -m pip install -r array-api-tests/requirements.txt
    - name: Build and install NumPy
      run: |
        python -m pip install . -v -Csetup-args=-Dallow-noblas=true -Csetup-args=-Dcpu-baseline=none -Csetup-args=-Dcpu-dispatch=none
    - name: Run the test suite
      env:
        ARRAY_API_TESTS_MODULE: numpy
        PYTHONWARNINGS: 'ignore::UserWarning::,ignore::DeprecationWarning::,ignore::RuntimeWarning::'
      run: |
        cd ${GITHUB_WORKSPACE}/array-api-tests
        pytest array_api_tests -v -c pytest.ini -n 4 --max-examples=1000 --derandomize --disable-deadline --xfails-file ${GITHUB_WORKSPACE}/tools/ci/array-api-xfails.txt

  ml_dtypes_compat:
    # Check a downstream dtype-extension project on both the current branch
    # and also NumPy 2.0.x runtime (but still built with current).
    needs: [smoke_test]
    runs-on: ubuntu-latest
    if: github.event_name != 'push'
    steps:
    - name: Checkout NumPy
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false
    - name: Checkout ml_dtypes
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        repository: jax-ml/ml_dtypes
        # Current ml_dtypes main, should be updated occasionally:
        ref: '9b8b2471a5ec5fd4c223693f677fcc2416847970'  # main as of 2026-05-11
        submodules: 'true'
        path: 'ml_dtypes'
        persist-credentials: false
    - name: Set up Python
      uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      with:
        python-version: '3.12'
    - name: Install build and test dependencies from PyPI
      run: |
        python -m pip install --upgrade pip
        python -m pip install -r requirements/build_requirements.txt
        python -m pip install -r requirements/test_requirements.txt
        python -m pip install --upgrade scikit-build-core cmake wheel
    - name: Build and install NumPy
      run: |
        python -m pip install . -v -Csetup-args=-Dallow-noblas=true -Csetup-args=-Dcpu-baseline=none -Csetup-args=-Dcpu-dispatch=none
    - name: Build and install ml_dtypes
      run: |
        cd ml_dtypes
        python -m pip install -e ".[dev]" --no-build-isolation
    - name: Run ml_dtypes tests with current NumPy
      run: |
        python -m pip show numpy
        cd ml_dtypes
        python -m pytest -n 2 ml_dtypes/tests
    - name: Replace NumPy with 2.0.x
      run: |
        python -m pip install --force-reinstall "numpy==2.0.*"
        python -m pip show numpy
    - name: Run ml_dtypes tests with NumPy 2.0.x
      run: |
        cd ml_dtypes
        python -m pytest -n 2 ml_dtypes/tests

  custom_checks:
    needs: [smoke_test]
    runs-on: ubuntu-latest
    if: github.event_name != 'push'
    steps:
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        submodules: recursive
        fetch-tags: true
        persist-credentials: false
    - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
      with:
        python-version: '3.12'
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        repository: numpy/numpy-release
        path: numpy-release
        persist-credentials: false
    - name: Check scipy-openblas version in release pipelines
      run: |
        python tools/check_openblas_version.py --req-files numpy-release/requirements/openblas_requirements.txt
    - name: Install build and test dependencies from PyPI
      run: |
        pip install -r requirements/build_requirements.txt
        pip install -r requirements/test_requirements.txt
        pip install "vulture!=2.15"
    - name: Build and install NumPy
      run: |
        # Install using the fastest way to build (no BLAS, no SIMD)
        spin build -j2 -- -Dallow-noblas=true -Dcpu-baseline=none -Dcpu-dispatch=none
    - name: Check build-internal dependencies
      run: |
        ninja -C build -t missingdeps
    - name: Check installed test and stub files
      run: |
        python tools/check_installed_files.py $(find ./build-install -path '*/site-packages/numpy')
    - name: Check for unreachable code paths in Python modules
      run: |
        # Need the explicit `bash -c` here because `grep` returns exit code 1 for no matches
        bash -c "! vulture . --min-confidence 100 --exclude doc/,vendored-meson/ | grep 'unreachable'"
    - name: Check usage of install_tag
      run: |
        rm -rf build-install
        ./vendored-meson/meson/meson.py install -C build --destdir ../build-install --tags=runtime,python-runtime,devel
        python tools/check_installed_files.py $(find ./build-install -path '*/site-packages/numpy') --no-tests

  Linux_Python_312_32bit_full:
    name: i686, cp312, full
    needs: [smoke_test]
    runs-on: ubuntu-latest
    container:
       # There are few options for i686 images at https://quay.io/organization/pypa,
       # use the glibc2.28 one
      image: quay.io/pypa/manylinux_2_28_i686

    steps:
    - name: Checkout and initialize submodules
      # actions/checkout doesn't work in a container image
      run: |
        git config --global --add safe.directory $PWD
        if [ $GITHUB_EVENT_NAME != pull_request ]; then
            git clone --recursive --branch=$GITHUB_REF_NAME https://github.com/${GITHUB_REPOSITORY}.git $GITHUB_WORKSPACE
            git reset --hard $GITHUB_SHA
        else
            git clone --recursive https://github.com/${GITHUB_REPOSITORY}.git $GITHUB_WORKSPACE
            git fetch origin $GITHUB_REF:my_ref_name
            git checkout $GITHUB_BASE_REF
            git -c user.email="you@example.com" merge --no-commit my_ref_name
        fi
        git submodule update --init --recursive

    - name: build
      run: |
        python3.12 -m venv venv
        source venv/bin/activate
        pip install --upgrade pip
        pip install -r requirements/ci32_requirements.txt
        pip install -r requirements/test_requirements.txt

        spin config-openblas --with-scipy-openblas=32
        export PKG_CONFIG_PATH=$(pwd)/.openblas
        python -m pip install . -v -Csetup-args="-Dallow-noblas=false"

    - name: test
      run: |
        source venv/bin/activate
        cd tools
        python -m pytest --pyargs numpy
