diff --git a/.azure/lint_docs-linux.yml b/.azure/lint_docs-linux.yml deleted file mode 100644 index b9e0c37892df..000000000000 --- a/.azure/lint_docs-linux.yml +++ /dev/null @@ -1,45 +0,0 @@ -parameters: - - name: "pythonVersion" - type: string - displayName: "Version of Python to use" - -jobs: - - job: 'Lint_Docs' - displayName: 'Lint and documentation' - pool: {vmImage: 'ubuntu-latest'} - - variables: - PIP_CACHE_DIR: $(Pipeline.Workspace)/.pip - RUST_DEBUG: 1 - - steps: - - checkout: self - - - task: UsePythonVersion@0 - inputs: - versionSpec: '${{ parameters.pythonVersion }}' - displayName: 'Use Python ${{ parameters.pythonVersion }}' - - - bash: tools/install_ubuntu_docs_dependencies.sh - displayName: 'Install docs dependencies' - - - bash: tox run -e docs,lint - displayName: 'Lint and docs' - - - bash: rm -rf docs/_build/html/{.doctrees,.buildinfo} - displayName: 'Clean up docs detritus' - - - task: ArchiveFiles@2 - inputs: - rootFolderOrFile: 'docs/_build/html' - archiveType: tar - archiveFile: '$(Build.ArtifactStagingDirectory)/html_docs.tar.gz' - verbose: true - - - task: PublishBuildArtifacts@1 - displayName: 'Upload docs artifact' - inputs: - pathtoPublish: '$(Build.ArtifactStagingDirectory)' - artifactName: 'html_docs' - Parallel: true - ParallelCount: 8 diff --git a/.azure/test-linux.yml b/.azure/test-linux.yml deleted file mode 100644 index a641ae215efd..000000000000 --- a/.azure/test-linux.yml +++ /dev/null @@ -1,225 +0,0 @@ -parameters: - - name: "pythonVersion" - type: string - displayName: "Version of Python to test" - - - name: "testRust" - type: boolean - - - name: "testImages" - type: boolean - - - name: "installOptionals" - type: boolean - default: false - - - name: "installFromSdist" - type: boolean - default: false - -jobs: - - job: "Linux_Tests_Python${{ replace(parameters.pythonVersion, '.', '') }}" - displayName: "Test Linux Rust & Python ${{ parameters.pythonVersion }}" - pool: {vmImage: 'ubuntu-latest'} - - variables: - QISKIT_SUPPRESS_PACKAGING_WARNINGS: Y - PIP_CACHE_DIR: $(Pipeline.Workspace)/.pip - QISKIT_TEST_CAPTURE_STREAMS: 1 - HAVE_VISUAL_TESTS_RUN: false - - steps: - - task: UsePythonVersion@0 - inputs: - versionSpec: '${{ parameters.pythonVersion }}' - name: 'usePython' - displayName: 'Use Python ${{ parameters.pythonVersion }}' - - - task: Cache@2 - inputs: - key: 'stestr | "$(Agent.OS)" | "${{ parameters.pythonVersion }}" | "$(Build.BuildNumber)"' - restoreKeys: | - stestr | "$(Agent.OS)" | "${{ parameters.pythonVersion }}" - stestr | "$(Agent.OS)" - stestr - path: .stestr - displayName: "Cache stestr" - - - ${{ if eq(parameters.testRust, true) }}: - # We need to avoid linking our crates into full Python extension libraries during Rust-only - # testing because Rust/PyO3 can't handle finding a static CPython interpreter. - - bash: cargo test --no-default-features - env: - # On Linux we link against `libpython` dynamically, but it isn't written into the rpath - # of the test executable (I'm not 100% sure why ---Jake). It's easiest just to forcibly - # include the correct place in the `dlopen` search path. - LD_LIBRARY_PATH: '$(usePython.pythonLocation)/lib:$LD_LIBRARY_PATH' - displayName: "Run Rust tests" - - - bash: | - set -e - python -m pip install --upgrade pip setuptools wheel virtualenv - virtualenv test-job - displayName: "Prepare venv" - - - ${{ if eq(parameters.installFromSdist, true) }}: - - bash: | - set -e - # Use stable Rust, rather than MSRV, to spot-check that stable builds properly. - rustup override set stable - source test-job/bin/activate - python -m pip install -U pip - python -m pip install -U build - python -m build --sdist . - python -m pip install -U \ - -c constraints.txt \ - -r requirements.txt \ - -r requirements-dev.txt \ - dist/qiskit-*.tar.gz - # Build and install both qiskit and qiskit-terra so that any optionals - # depending on `qiskit` will resolve correctly. - displayName: "Install Terra from sdist" - - - ${{ if eq(parameters.installFromSdist, false) }}: - - bash: | - set -e - source test-job/bin/activate - python -m pip install -U \ - -c constraints.txt \ - -r requirements.txt \ - -r requirements-dev.txt \ - -e . - # Build and install both qiskit and qiskit-terra so that any optionals - # depending on `qiskit` will resolve correctly. - displayName: "Install Terra directly" - - - ${{ if eq(parameters.installOptionals, true) }}: - - bash: | - set -e - source test-job/bin/activate - python -m pip install -r requirements-optional.txt -c constraints.txt - python -m pip check - displayName: "Install optional packages" - - - bash: | - set -e - sudo apt-get update - sudo apt-get install -y graphviz - displayName: 'Install optional non-Python dependencies' - - - bash: | - set -e - source test-job/bin/activate - python tools/report_numpy_state.py - mkdir -p /tmp/terra-tests - cp -r test /tmp/terra-tests/. - cp .stestr.conf /tmp/terra-tests/. - cp -r .stestr /tmp/terra-tests/. || : - pushd /tmp/terra-tests - export PYTHONHASHSEED=$(python -S -c "import random; print(random.randint(1, 4294967295))") - echo "PYTHONHASHSEED=$PYTHONHASHSEED" - stestr run - popd - env: - QISKIT_PARALLEL: FALSE - RUST_BACKTRACE: 1 - displayName: 'Run Python tests' - - - bash: | - set -e - source test-job/bin/activate - cp tools/subunit_to_junit.py /tmp/terra-tests/. - python -m pip install -U junitxml - pushd /tmp/terra-tests - mkdir -p junit - stestr last --subunit | ./subunit_to_junit.py -o junit/test-results.xml - pushd .stestr - ls | grep -P "^\d" | xargs -d "\n" rm -f - popd - popd - cp -r /tmp/terra-tests/junit . - cp -r /tmp/terra-tests/.stestr . - displayName: 'Generate results' - condition: succeededOrFailed() - - - task: PublishTestResults@2 - condition: succeededOrFailed() - inputs: - testResultsFiles: '**/test-*.xml' - testRunTitle: 'Test results for Linux Python ${{ parameters.pythonVersion }}' - - - task: CopyFiles@2 - inputs: - contents: '**/*.png' - targetFolder: $(Build.ArtifactStagingDirectory) - displayName: 'Copy images on test failure' - condition: failed() - - - task: PublishBuildArtifacts@1 - inputs: - pathtoPublish: '$(Build.ArtifactStagingDirectory)' - artifactName: 'drop_linux' - displayName: 'Publish images on test failure' - condition: failed() - - - ${{ if eq(parameters.testImages, true) }}: - - bash: | - set -e - virtualenv image_tests - image_tests/bin/python -m pip install -U \ - -c constraints.txt \ - -r requirements.txt \ - -r requirements-dev.txt \ - -r requirements-optional.txt \ - -e . - sudo apt-get update - sudo apt-get install -y graphviz pandoc - image_tests/bin/pip check - displayName: 'Install dependencies' - - - bash: | - echo "##vso[task.setvariable variable=HAVE_VISUAL_TESTS_RUN;]true" - image_tests/bin/python -m unittest discover -v test/visual - displayName: 'Run image test' - env: - # Needed to suppress a warning in jupyter-core 5.x by eagerly migrating to - # a new internal interface that will be the default in jupyter-core 6.x. - # This variable should become redundant on release of jupyter-core 6. - JUPYTER_PLATFORM_DIRS: 1 - - - task: ArchiveFiles@2 - displayName: Archive visual test failure diffs - inputs: - rootFolderOrFile: 'test/visual/mpl/visual_test_failures' - includeRootFolder: false - archiveType: tar - archiveFile: '$(Build.ArtifactStagingDirectory)/visual_test_failures.tar.gz' - verbose: true - condition: and(failed(), eq(variables.HAVE_VISUAL_TESTS_RUN, 'true')) - - - task: ArchiveFiles@2 - displayName: Archive circuit results - inputs: - rootFolderOrFile: 'test/visual/mpl/circuit/circuit_results' - archiveType: tar - archiveFile: '$(Build.ArtifactStagingDirectory)/circuit_results.tar.gz' - verbose: true - condition: and(failed(), eq(variables.HAVE_VISUAL_TESTS_RUN, 'true')) - - - task: ArchiveFiles@2 - displayName: Archive graph results - inputs: - rootFolderOrFile: 'test/visual/mpl/graph/graph_results' - archiveType: tar - archiveFile: '$(Build.ArtifactStagingDirectory)/graph_results.tar.gz' - verbose: true - condition: and(failed(), eq(variables.HAVE_VISUAL_TESTS_RUN, 'true')) - - - task: PublishBuildArtifacts@1 - displayName: 'Publish image test failure diffs' - inputs: - pathtoPublish: '$(Build.ArtifactStagingDirectory)' - artifactName: 'image_test_failure_img_diffs' - Parallel: true - ParallelCount: 8 - condition: and(failed(), eq(variables.HAVE_VISUAL_TESTS_RUN, 'true')) diff --git a/.azure/test-macos.yml b/.azure/test-macos.yml deleted file mode 100644 index 9e9620e8ecbb..000000000000 --- a/.azure/test-macos.yml +++ /dev/null @@ -1,101 +0,0 @@ -parameters: - - name: "pythonVersion" - type: string - displayName: "Version of Python to test" - - - name: "installOptionals" - type: boolean - default: false - -jobs: - - job: "MacOS_Tests_Python${{ replace(parameters.pythonVersion, '.', '') }}" - displayName: "Test macOS Python ${{ parameters.pythonVersion }}" - pool: {vmImage: 'macOS-13'} - - variables: - QISKIT_SUPPRESS_PACKAGING_WARNINGS: Y - PIP_CACHE_DIR: $(Pipeline.Workspace)/.pip - QISKIT_TEST_CAPTURE_STREAMS: 1 - - steps: - - task: UsePythonVersion@0 - inputs: - versionSpec: '${{ parameters.pythonVersion }}' - displayName: 'Use Python ${{ parameters.pythonVersion }}' - - - task: Cache@2 - inputs: - key: 'stestr | "$(Agent.OS)" | "${{ parameters.pythonVersion }}" | "$(Build.BuildNumber)"' - restoreKeys: | - stestr | "$(Agent.OS)" | "${{ parameters.pythonVersion }}" - stestr | "$(Agent.OS)" - stestr - path: .stestr - displayName: "Cache stestr" - - - bash: | - set -e - python -m pip install --upgrade pip setuptools wheel virtualenv - virtualenv test-job - source test-job/bin/activate - python -m pip install -U pip setuptools wheel - python -m pip install -U \ - -c constraints.txt \ - -r requirements.txt \ - -r requirements-dev.txt \ - -e . - # Build and install both qiskit and qiskit-terra so that any optionals - # depending on `qiskit` will resolve correctly. - pip check - displayName: 'Install dependencies' - - - ${{ if eq(parameters.installOptionals, true) }}: - - bash: | - set -e - source test-job/bin/activate - pip install -r requirements-optional.txt -c constraints.txt - displayName: "Install optional packages" - - - bash: | - set -e - source test-job/bin/activate - python tools/report_numpy_state.py - export PYTHONHASHSEED=$(python -S -c "import random; print(random.randint(1, 4294967295))") - echo "PYTHONHASHSEED=$PYTHONHASHSEED" - stestr run - env: - QISKIT_PARALLEL: FALSE - RUST_BACKTRACE: 1 - displayName: "Run tests" - - - bash: | - set -e - source test-job/bin/activate - python -m pip install -U junitxml - mkdir -p junit - stestr last --subunit | tools/subunit_to_junit.py -o junit/test-results.xml - pushd .stestr - ls | grep -e '^[[:digit:]]' | xargs -n1 rm -f - popd - displayName: "Generate results" - condition: succeededOrFailed() - - - task: PublishTestResults@2 - condition: succeededOrFailed() - inputs: - testResultsFiles: '**/test-*.xml' - testRunTitle: 'Test results for macOS Python ${{ parameters.pythonVersion }}' - - - task: CopyFiles@2 - inputs: - contents: '**/*.png' - targetFolder: $(Build.ArtifactStagingDirectory) - displayName: 'Copy images on test failure' - condition: failed() - - - task: PublishBuildArtifacts@1 - inputs: - pathtoPublish: '$(Build.ArtifactStagingDirectory)' - artifactName: 'drop_macos' - displayName: 'Publish images on test failure' - condition: failed() diff --git a/.azure/test-windows.yml b/.azure/test-windows.yml deleted file mode 100644 index 8d86456bd72f..000000000000 --- a/.azure/test-windows.yml +++ /dev/null @@ -1,108 +0,0 @@ -parameters: - - name: "pythonVersion" - type: string - displayName: "Versions of Python to test" - - - name: "installOptionals" - type: boolean - default: false - -jobs: - - job: "Windows_Tests_Python${{ replace(parameters.pythonVersion, '.', '') }}" - displayName: "Test Windows Python ${{ parameters.pythonVersion }}" - pool: {vmImage: 'windows-latest'} - - variables: - QISKIT_SUPPRESS_PACKAGING_WARNINGS: Y - QISKIT_TEST_CAPTURE_STREAMS: 1 - - steps: - - task: UsePythonVersion@0 - inputs: - versionSpec: '${{ parameters.pythonVersion }}' - displayName: 'Use Python ${{ parameters.pythonVersion }}' - - - task: Cache@2 - inputs: - key: 'stestr | "$(Agent.OS)" | "${{ parameters.pythonVersion }}" | "$(Build.BuildNumber)"' - restoreKeys: | - stestr | "$(Agent.OS)" | "${{ parameters.pythonVersion }}" - stestr | "$(Agent.OS)" - stestr - path: .stestr - displayName: Cache stestr - - - bash: | - set -e - python -m pip install --upgrade pip setuptools wheel virtualenv - virtualenv test-job - source test-job/Scripts/activate - python -m pip install -U pip setuptools wheel - python -m pip install -U \ - -c constraints.txt \ - -r requirements.txt \ - -r requirements-dev.txt \ - -e . - # Build and install both qiskit and qiskit-terra so that any optionals - # depending on `qiskit` will resolve correctly. - pip check - displayName: 'Install dependencies' - - - ${{ if eq(parameters.installOptionals, true) }}: - - bash: | - set -e - source test-job/Scripts/activate - pip install -c constraints.txt -r requirements-optional.txt - pip check - displayName: "Install optional packages" - - - bash: | - set -e - chcp.com 65001 - source test-job/Scripts/activate - python tools/report_numpy_state.py - export PYTHONHASHSEED=$(python -S -c "import random; print(random.randint(1, 1024))") - echo "PYTHONHASHSEED=$PYTHONHASHSEED" - stestr run - env: - LANG: 'C.UTF-8' - PYTHONIOENCODING: 'utf-8:backslashreplace' - QISKIT_PARALLEL: FALSE - RUST_BACKTRACE: 1 - displayName: 'Run tests' - - - bash: | - set -e - chcp.com 65001 - source test-job/Scripts/activate - python -m pip install -U junitxml - mkdir -p junit - stestr last --subunit | python tools/subunit_to_junit.py -o junit/test-results.xml - pushd .stestr - ls | grep -P "^\d" | xargs -d "\n" rm -f - popd - condition: succeededOrFailed() - env: - LANG: 'C.UTF-8' - PYTHONIOENCODING: 'utf-8:backslashreplace' - displayName: 'Generate results' - - - task: PublishTestResults@2 - condition: succeededOrFailed() - inputs: - testResultsFiles: '**/test-*.xml' - testRunTitle: 'Test results for Windows Python ${{ parameters.pythonVersion }}' - - - task: CopyFiles@2 - condition: failed() - displayName: 'Copy images' - inputs: - contents: '**/*.png' - targetFolder: $(Build.ArtifactStagingDirectory) - - - task: PublishBuildArtifacts@1 - condition: failed() - displayName: 'Publish images' - inputs: - pathtoPublish: '$(Build.ArtifactStagingDirectory)' - artifactName: 'drop_windows' diff --git a/.github/workflows/lint_docs.yml b/.github/workflows/lint_docs.yml new file mode 100644 index 000000000000..28ed57988ca4 --- /dev/null +++ b/.github/workflows/lint_docs.yml @@ -0,0 +1,58 @@ +--- +name: Lint and Docs +on: + workflow_call: + + +concurrency: + group: ${{ github.repository }}-${{ github.ref }}-${{ github.head_ref }}-${{ github.workflow }} + cancel-in-progress: true +jobs: + docs: + # if: github.repository_owner == 'Qiskit' + name: Docs + runs-on: ubuntu-latest + strategy: + fail-fast: true + steps: + - uses: actions/checkout@v4 + with: + # We need to fetch the whole history so 'reno' can do its job and we can inspect tags. + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.9' + - name: Install ubuntu docs dependencies + run: tools/install_ubuntu_docs_dependencies.sh + - name: Run docs + run: tox -e docs + - name: Clean up docs detritus + run: rm -rf docs/_build/html/{.doctrees,.buildinfo} + - name: Store built documentation artifact + uses: actions/upload-artifact@v4 + with: + name: qiskit-docs + path: | + ./docs/_build/html/* + if-no-files-found: error + lint: + if: github.repository_owner == 'Qiskit' + name: Lint + runs-on: ubuntu-latest + strategy: + fail-fast: true + steps: + - uses: actions/checkout@v4 + with: + # We need to fetch the whole history so 'reno' can do its job and we can inspect tags. + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.9' + - name: Install ubuntu docs dependencies + run: tools/install_ubuntu_docs_dependencies.sh + - name: Run lint + run: tox -e lint + \ No newline at end of file diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 000000000000..2166bd04151b --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,34 @@ +--- +# A schedule only runs on branches that match include rules from _both_ `main` +# and the branch itself. On `main`, we blanket include all branches that might +# want to be enabled, then particular branches can override it to exclude +# themselves by removing themselves from the trigger list. For example, old +# stable branches can remove `stable/*` from their copy of this file once they +# reach their end-of-life. +name: Nightly Main tests +on: + schedule: + - cron: "20 6 * * *" + workflow_dispatch: + +jobs: + nightly-tests: + # Nightly cron job. + # + # For this to run on a branch, the `schedules` trigger up at the top of this + # file needs to match on _both_ `main` and the branch itself. + # if: ${{ github.repository_owner == 'Qiskit'}} + uses: ./.github/workflows/on-nightly.yml + nightly-failure: + name: Create comment on failed test run + # if: ${{ github.repository_owner == 'Qiskit' && failure() }} + if: ${{ failure() }} + runs-on: ubuntu-latest + steps: + - uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: 7864 + body: | + Nightly matrix test run failed at commit ${{ github.sha }}. + + _Logs_: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}. diff --git a/.github/workflows/on-merge-queue.yml b/.github/workflows/on-merge-queue.yml new file mode 100644 index 000000000000..488a865903e4 --- /dev/null +++ b/.github/workflows/on-merge-queue.yml @@ -0,0 +1,45 @@ +--- +# Merge queue. A PR that reaches here has already passed the more rigorous PR +# suite, so is very likely to pass. The main reasons for failures here are +# flaky VMs timing out (which we can't do much about), or a merge conflict +# with another PR that is also in the merge queue. +# +# There's no reason to have multiple stages in this case, because we're +# expecting it to pass. Having more than one stage frustrates parallel +# throughput in low-contention cases, and guarantees a longer critical path. +name: Merge queue +on: + merge_group: + branches: [main, "stable/*"] +concurrency: + group: ${{ github.repository }}-${{ github.ref }}-${{ github.head_ref }}-${{ github.workflow }} + cancel-in-progress: true + +jobs: + docs_lint: + # if: github.repository_owner == 'Qiskit' + name: Docs and Lint + uses: ./.github/workflows/lint_docs.yml + test-linux: + # if: github.repository_owner == 'Qiskit' + uses: ./.github/workflows/test-linux.yml + strategy: + matrix: + python-version: ["3.9", "3.13"] + with: + python-version: ${{ matrix.python-version }} + install-optionals: ${{ matrix.python-version == '3.9' }} + test-rust: ${{ matrix.python-version == '3.9' }} + test-images: ${{ matrix.python-version == '3.9' }} + test-mac: + # if: github.repository_owner == 'Qiskit' + uses: ./.github/workflows/test-mac.yml + with: + python-version: "3.13" + install-optionals: false + test-windows: + # if: github.repository_owner == 'Qiskit' + uses: ./.github/workflows/test-windows.yml + with: + python-version: "3.13" + install-optionals: false diff --git a/.github/workflows/on-nightly.yml b/.github/workflows/on-nightly.yml new file mode 100644 index 000000000000..4fdfd8105d5f --- /dev/null +++ b/.github/workflows/on-nightly.yml @@ -0,0 +1,36 @@ +--- +name: Nightly complete matrix tests +on: + workflow_call: + +concurrency: + group: ${{ github.repository }}-${{ github.ref }}-${{ github.head_ref }} + cancel-in-progress: true + +jobs: + nightly-linux: + uses: ./.github/workflows/test-linux.yml + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + with: + python-version: ${{ matrix.python-version }} + test-images: false + test-rust: false + install-optionals: true + nightly-mac: + uses: ./.github/workflows/test-mac.yml + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + with: + python-version: ${{ matrix.python-version }} + install-optionals: true + nightly-windows: + uses: ./.github/workflows/test-windows.yml + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + with: + python-version: ${{ matrix.python-version }} + install-optionals: true diff --git a/.github/workflows/on-pull-request.yml b/.github/workflows/on-pull-request.yml new file mode 100644 index 000000000000..b75df7d704fa --- /dev/null +++ b/.github/workflows/on-pull-request.yml @@ -0,0 +1,80 @@ +--- +# Full PR suite. PRs need to pass this pipeline in order to be moved to the +# merge queue, where they'll use the next rule as the branch-protection rule +# for the final merge to the base branch. +name: Pull Requests +on: + pull_request: + branches: [main, "stable/*"] + merge_group: + +concurrency: + group: ${{ github.repository }}-${{ github.ref }}-${{ github.head_ref }} + cancel-in-progress: true +jobs: + # The preliminary stage should be small in both total runtime (including + # provisioning) and resources required. About half of PR commits result in + # a CI failure, and over 90% of these are in linting, documention or a test + # failure that would affect _any_ OS or Python version. The goal in the + # first stage is to catch the vast majority of failures with minimal cost. + lint-docs: + # if: github.repository_owner == 'Qiskit' + name: Docs and Lint + uses: ./.github/workflows/lint_docs.yml + preliminary-tests: + name: Preliminary Tests + uses: ./.github/workflows/test-linux.yml + # if: github.repository_owner == 'Qiskit' + with: + python-version: "3.9" + # A PR is more likely to fail CI because it introduces a logic error + # into an existing test than because it adds a new test / optional + # dependency that isn't accounted for in the test-skipping logic + # (and such a failure would need fewer iterations to fix). We want + # to fail fast in the first CI stage. + install-optionals: true + test-rust: true + test-images: true + + # The rest of the PR pipeline is to test the oldest and newest supported + # versions of Python. It's very rare for a failure to be specific to an + # intermediate version of Python, so we just catch those in the cron-job + # pipeline to reduce the amount of resources used. + main-tests-ubuntu: + # if: github.repository_owner == 'Qiskit' + name: Main Tests (ubuntu-latest) + needs: [lint-docs, preliminary-tests] + strategy: + matrix: + python_version: ["3.13"] + uses: ./.github/workflows/test-linux.yml + with: + python-version: ${{ matrix.python_version }} + test-rust: false + test-images: false + install-from-sdist: true + install-optionals: false + + main-tests-mac: + # if: github.repository_owner == 'Qiskit' + name: Main Tests (macos) + needs: [lint-docs, preliminary-tests] + strategy: + matrix: + python_version: ["3.9", "3.13"] + uses: ./.github/workflows/test-mac.yml + with: + python-version: ${{ matrix.python_version }} + install-optionals: ${{ matrix.python_version == '3.9'}} + + main-tests-windows: + # if: github.repository_owner == 'Qiskit' + name: Main Tests (windows-latest) + needs: [lint-docs, preliminary-tests] + strategy: + matrix: + python_version: ["3.9", "3.13"] + uses: ./.github/workflows/test-windows.yml + with: + python-version: ${{ matrix.python_version }} + install-optionals: ${{ matrix.python_version == '3.9'}} diff --git a/.github/workflows/on-push.yml b/.github/workflows/on-push.yml new file mode 100644 index 000000000000..219cd118a685 --- /dev/null +++ b/.github/workflows/on-push.yml @@ -0,0 +1,26 @@ +--- +# Push to main or the stable branches. The triggering branches also need to +# be in the triggers at the top of this file. +name: Push +on: + push: + branches: [main, "stable/*"] + +jobs: + tests-mac: + # if: github.repository_owner == 'Qiskit' + uses: ./.github/workflows/test-mac.yml + strategy: + matrix: + python-version: ["3.9", "3.13"] + with: + python-version: ${{ matrix.python-version }} + install-optionals: false + test-linux: + # if: github.repository_owner == 'Qiskit' + uses: ./.github/workflows/test-linux.yml + with: + python-version: "3.9" + test-rust: true + test-images: true + install-optionals: false diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml new file mode 100644 index 000000000000..823cc1e1d134 --- /dev/null +++ b/.github/workflows/test-linux.yml @@ -0,0 +1,171 @@ +--- +name: Test Linux +on: + workflow_call: + inputs: + python-version: + description: > + Python version to currently test + type: string + required: true + test-rust: + description: > + Decides whether we perform rust tests + type: boolean + required: true + test-images: + description: > + Decides whether we perform image tests + type: boolean + required: true + install-optionals: + description: > + Decides whether we install optyional dependencies + type: boolean + default: false + + install-from-sdist: + description: > + Decides whether we perform rust tests + type: boolean + default: false + + merge_group: + +jobs: + tests-linux: + # if: github.repository_owner == 'Qiskit' + name: ubuntu-latest-tests-Python-${{ inputs.python-version }} + runs-on: ubuntu-latest + env: + QISKIT_SUPPRESS_PACKAGING_WARNINGS: Y + PIP_CACHE_DIR: ${{ github.workspace }}/.pip + QISKIT_TEST_CAPTURE_STREAMS: 1 + HAVE_VISUAL_TESTS_RUN: false + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v4 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@1.70 + if: inputs.python-version == '3.9' + - name: Set up Python ${{ inputs.python-version }} + id: python-outputs + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version }} + - name: Cache stestr + uses: actions/cache@v4 + with: + key: 'stestr | "${{ runner.os }}" | "${{ inputs.python-version }}" | "${{ github.run_id }}"' + restore-keys: | + stestr | "${{ runner.os }}" | "${{ inputs.python-version }}" + stestr | "${{ runner.os }}" + stestr + path: .stestr + - name: Run Rust Tests + if: inputs.test-rust == true + # We need to avoid linking our crates into full Python extension libraries during Rust-only + # testing because Rust/PyO3 can't handle finding a static CPython interpreter. + run: cargo test --no-default-features + env: + # On Linux we link against `libpython` dynamically, but it isn't written into the rpath + # of the test executable (I'm not 100% sure why ---Jake). It's easiest just to forcibly + # include the correct place in the `dlopen` search path. + LD_LIBRARY_PATH: "${{ steps.python-outputs.outputs.python-path }}/lib:${{env.LD_LIBRARY_PATH}}" + - name: Prepare venv + run: | + set -e + python -m pip install --upgrade pip setuptools wheel virtualenv + virtualenv test-job + - name: Install Qiskit from sdist + if: ${{ inputs.install-from-sdist }} + run: | + set -e + # Use stable Rust, rather than MSRV, to spot-check that stable builds properly. + rustup override set stable + source test-job/bin/activate + python -m pip install -U pip + python -m pip install -U build + python -m build --sdist . + python -m pip install -U \ + -c constraints.txt \ + -r requirements.txt \ + -r requirements-dev.txt \ + dist/qiskit-*.tar.gz + # Build and install both qiskit and qiskit-terra so that any optionals + # depending on `qiskit` will resolve correctly. + - name: Install Qiskit directly + if: ${{ !inputs.install-from-sdist }} + run: | + set -e + source test-job/bin/activate + python -m pip install -U \ + -c constraints.txt \ + -r requirements.txt \ + -r requirements-dev.txt \ + -e . + # Build and install both qiskit and qiskit-terra so that any optionals + # depending on `qiskit` will resolve correctly. + - name: Install Optional packages + run: | + set -e + source test-job/bin/activate + python -m pip install -r requirements-optional.txt -c constraints.txt + python -m pip check + if: ${{ inputs.install-optionals }} + - name: Install optional non-Python dependencies + run: | + set -e + sudo apt-get update + sudo apt-get install -y graphviz + if: ${{ inputs.install-optionals }} + - name: Run Python tests + run: | + set -e + source test-job/bin/activate + python tools/report_numpy_state.py + mkdir -p /tmp/terra-tests + cp -r test /tmp/terra-tests/. + cp .stestr.conf /tmp/terra-tests/. + cp -r .stestr /tmp/terra-tests/. || : + pushd /tmp/terra-tests + export PYTHONHASHSEED=$(python -S -c "import random; print(random.randint(1, 4294967295))") + echo "PYTHONHASHSEED=$PYTHONHASHSEED" + stestr run + popd + env: + QISKIT_PARALLEL: FALSE + RUST_BACKTRACE: 1 + - name: Install Image dependencies + run: | + set -e + virtualenv image_tests + image_tests/bin/python -m pip install -U \ + -c constraints.txt \ + -r requirements.txt \ + -r requirements-dev.txt \ + -r requirements-optional.txt \ + -e . + sudo apt-get update + sudo apt-get install -y graphviz pandoc + image_tests/bin/pip check + if: ${{ inputs.test-images }} + - name: Run image tests + run: | + echo "##vso[task.setvariable variable=HAVE_VISUAL_TESTS_RUN;]true" + image_tests/bin/python -m unittest discover -v test/visual + if: ${{ inputs.test-images }} + env: + # Needed to suppress a warning in jupyter-core 5.x by eagerly migrating to + # a new internal interface that will be the default in jupyter-core 6.x. + # This variable should become redundant on release of jupyter-core 6. + JUPYTER_PLATFORM_DIRS: 1 + - name: Store image tests diff + uses: actions/upload-artifact@v4 + if: ${{ inputs.test-images && failure()}} + with: + name: image-test-failure-img-diffs + path: | + ./test/visual/mpl/graph/graph_results + if-no-files-found: error diff --git a/.github/workflows/test-mac.yml b/.github/workflows/test-mac.yml new file mode 100644 index 000000000000..408ee004e228 --- /dev/null +++ b/.github/workflows/test-mac.yml @@ -0,0 +1,88 @@ +--- +name: Test MacOS +on: + workflow_call: + inputs: + python-version: + description: > + Python version to currently test + type: string + required: true + + install-optionals: + description: > + Decides whether we install optyional dependencies + type: boolean + default: false + + merge_group: + +jobs: + tests-mac: + # if: github.repository_owner == 'Qiskit' + name: macOS-arm64-tests-Python-${{ inputs.python-version }} + runs-on: macOS-14 + env: + QISKIT_SUPPRESS_PACKAGING_WARNINGS: Y + PIP_CACHE_DIR: $(Pipeline.Workspace)/.pip + QISKIT_TEST_CAPTURE_STREAMS: 1 + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v4 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@1.70 + if: inputs.python-version == '3.9' + - name: Set up Python ${{ inputs.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version }} + architecture: arm64 + - name: Cache stestr + uses: actions/cache@v4 + with: + key: 'stestr | "${{ runner.os }}" | "${{ inputs.python-version }}" | "${{ github.run_id }}"' + restore-keys: | + stestr | "${{ runner.os }}" | "${{ inputs.python-version }}" + stestr | "${{ runner.os }}" + stestr + path: .stestr + - name: "Install dependencies" + run: | + set -e + python -m pip install --upgrade pip setuptools wheel virtualenv + virtualenv test-job + source test-job/bin/activate + python -m pip install -U pip setuptools wheel + python -m pip install -U \ + -c constraints.txt \ + -r requirements.txt \ + -r requirements-dev.txt \ + -e . + # Build and install both qiskit and qiskit-terra so that any optionals + # depending on `qiskit` will resolve correctly. + pip check + - name: "Install optionals" + run: | + set -e + source test-job/bin/activate + pip install -r requirements-optional.txt -c constraints.txt + if: ${{ inputs.install-optionals }} + - name: "Run tests" + run: | + set -e + source test-job/bin/activate + python tools/report_numpy_state.py + export PYTHONHASHSEED=$(python -S -c "import random; print(random.randint(1, 4294967295))") + echo "PYTHONHASHSEED=$PYTHONHASHSEED" + stestr run + env: + QISKIT_PARALLEL: FALSE + RUST_BACKTRACE: 1 + - name: Copy and Publish images + uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: copied-images + path: "**/*.png" + if-no-files-found: error diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml new file mode 100644 index 000000000000..6f31924c7f4e --- /dev/null +++ b/.github/workflows/test-windows.yml @@ -0,0 +1,88 @@ +--- +name: Test Windows +on: + workflow_call: + inputs: + python-version: + description: > + Python version to currently test + type: string + required: true + + install-optionals: + description: > + Decides whether we install optyional dependencies + type: boolean + default: false + + merge_group: + +jobs: + test-windows: + # if: github.repository_owner == 'Qiskit' + name: windows-latest-tests-Python-${{ inputs.python-version }} + runs-on: windows-latest + env: + QISKIT_SUPPRESS_PACKAGING_WARNINGS: Y + QISKIT_TEST_CAPTURE_STREAMS: 1 + strategy: + fail-fast: false + steps: + - uses: actions/checkout@v4 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@1.70 + if: inputs.python-version == '3.9' + - name: Set up Python ${{ inputs.python-version }} + id: python-outputs + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version }} + - name: Cache stestr + uses: actions/cache@v4 + with: + key: 'stestr | "${{ runner.os }}" | "${{ inputs.python-version }}" | "${{ github.run_id }}"' + restore-keys: | + stestr | "${{ runner.os }}" | "${{ inputs.python-version }}" + stestr | "${{ runner.os }}" + stestr + path: .stestr + - name: Install dependencies + run: | + python -m pip install --upgrade pip setuptools wheel virtualenv + python -m virtualenv test-job + .\test-job\Scripts\activate + python -m pip install -U pip setuptools wheel + python -m pip install -U ` + -c constraints.txt ` + -r requirements.txt ` + -r requirements-dev.txt ` + -e . + # Build and install both qiskit and qiskit-terra so that any optionals + # depending on `qiskit` will resolve correctly. + pip check + - name: Install Optional packages + run: | + .\test-job\Scripts\activate + pip install -c constraints.txt -r requirements-optional.txt + pip check + if: ${{ inputs.install-optionals }} + - name: Run Tests + run: | + chcp.com 65001 + .\test-job\Scripts\activate + python tools/report_numpy_state.py + $Env:PYTHONHASHSEED=$(python -S -c "import random; print(random.randint(1, 1024))") + echo "PYTHONHASHSEED=$PYTHONHASHSEED" + stestr run + env: + LANG: "C.UTF-8" + PYTHONIOENCODING: "utf-8:backslashreplace" + QISKIT_PARALLEL: FALSE + RUST_BACKTRACE: 1 + - name: Copy and Publish images + uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: copied-images + path: "**/*.png" + if-no-files-found: error diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 39439f7dd059..000000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,54 +0,0 @@ ---- -name: Tests -on: - push: - branches: [ main, 'stable/*' ] - pull_request: - branches: [ main, 'stable/*' ] - merge_group: - -concurrency: - group: ${{ github.repository }}-${{ github.ref }}-${{ github.head_ref }} - cancel-in-progress: true -jobs: - tests: - if: github.repository_owner == 'Qiskit' - name: macOS-arm64-tests-Python-${{ matrix.python-version }} - runs-on: macOS-14 - strategy: - fail-fast: false - matrix: - # Normally we test min and max version but we can't run python - # 3.9 on arm64 until actions/setup-python#808 is resolved - python-version: ["3.10", "3.13"] - steps: - - uses: actions/checkout@v4 - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@1.70 - if: matrix.python-version == '3.10' - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - architecture: arm64 - - name: 'Install dependencies' - run: | - python -m pip install -U -r requirements.txt -c constraints.txt - python -m pip install -U -r requirements-dev.txt -c constraints.txt - python -m pip install -c constraints.txt -e . - if: matrix.python-version == '3.10' - env: - QISKIT_NO_CACHE_GATES: 1 - - name: 'Install dependencies' - run: | - python -m pip install -U -r requirements.txt -c constraints.txt - python -m pip install -U -r requirements-dev.txt -c constraints.txt - python -m pip install -c constraints.txt -e . - if: matrix.python-version == '3.13' - - name: 'Install optionals' - run: | - python -m pip install -r requirements-optional.txt -c constraints.txt - python tools/report_numpy_state.py - if: matrix.python-version == '3.10' - - name: 'Run tests' - run: stestr run diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 94ed7d1fad7f..000000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,249 +0,0 @@ -# Trigger types. This needs to include all the triggers for the different -# pipelines we might want to run. -# -# Each of these individually triggers the pipeline compilation step, but it's -# the templating expressions in the `stages` list that actually set what runs. -trigger: - branches: - include: - - 'main' - - 'stable/*' - - 'gh-readonly-queue/*' - -pr: - autoCancel: true - branches: - include: - - '*' - -# A schedule only runs on branches that match include rules from _both_ `main` -# and the branch itself. On `main`, we blanket include all branches that might -# want to be enabled, then particular branches can override it to exclude -# themselves by removing themselves from the trigger list. For example, old -# stable branches can remove `stable/*` from their copy of this file once they -# reach their end-of-life. -schedules: - - cron: "20 6 * * *" - displayName: "Complete matrix test" - branches: - include: [ "main", "stable/*" ] - always: false # Only run if the code changed since the last cron sync. - - -# Configuration. In theory a manual trigger on the Azure website or embedding -# this pipeline as a template can override these, but we're not interested in -# that. We just want to give names to things to make it easier to read. -parameters: - - name: "supportedPythonVersions" - displayName: "All supported versions of Python" - type: object - default: ["3.9", "3.10", "3.11", "3.12", "3.13"] - - - name: "minimumPythonVersion" - displayName: "Minimum supported version of Python" - type: string - default: "3.9" - - - name: "maximumPythonVersion" - displayName: "Maximum supported version of Python" - type: string - default: "3.13" - - # These two versions of Python can be chosen somewhat arbitrarily, but we get - # slightly better coverage per PR if they're neither the maximum nor minimum - # supported versions. - - - name: "branchPushPythonVersion" - displayName: "Version of Python to run simple tests on pushes to protected branches" - type: string - default: "3.9" - - # Sync with 'python-version' in '.github/workflows/docs_deploy.yml'. - - name: "documentationPythonVersion" - displayName: "Version of Python to use to build Sphinx documentation" - type: string - default: "3.9" - -# Conditional compilation logic. This is all the logic for "what to run"; each -# stage in turn delegates to various templates in ".azure" that provide the -# information on "how to run" them. -# -# The conditional conditions use the template-expression syntax `${{ }}` so only -# the relevant stages even appear in the job list. The documentation on the -# expression syntax is here: -# -# https://docs.microsoft.com/en-us/azure/devops/pipelines/process/expressions?view=azure-devops -# -# Not all variables are available at template-expansion time - this happens -# before any machine is provisioned, so mostly you can access things from -# GitHub, but nothing about the actual machine. This is the list of variables -# (look in the "available in templates" column): -# -# https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml -# -# The main differentiator is `variables['Build.Reason']`, which contains the -# trigger type: -# - cron (`Schedule`) -# - push to a git reference like a branch or tag (`IndividualCI`) -# - push to a pull request (`PullRequest`) -# For `IndividualCI` (push/merge to a branch/tag on Qiskit/qiskit-terra), you -# need to examine `variables['Build.SourceBranch']` to determine whether it's a -# branch or a tag, and if a branch, then whether it's in the merge queue or a -# push to a "real" branch. -stages: - # Nightly cron job. - # - # For this to run on a branch, the `schedules` trigger up at the top of this - # file needs to match on _both_ `main` and the branch itself. - - ${{ if eq(variables['Build.Reason'], 'Schedule') }}: - - stage: "Nightly" - displayName: "Nightly complete matrix tests" - jobs: - - ${{ each version in parameters.supportedPythonVersions }}: - - template: ".azure/test-linux.yml" - parameters: - pythonVersion: ${{ version }} - testImages: false - testRust: false - installOptionals: true - - - template: ".azure/test-macos.yml" - parameters: - pythonVersion: ${{ version }} - installOptionals: true - - - template: ".azure/test-windows.yml" - parameters: - pythonVersion: ${{ version }} - installOptionals: true - - - stage: "Nightly_Failure" - displayName: "Comment on nightly failure" - dependsOn: "Nightly" - condition: failed() - pool: {vmImage: 'ubuntu-latest'} - jobs: - - job: "Comment" - steps: - - task: GitHubComment@0 - inputs: - gitHubConnection: Qiskit - repositoryName: ${{ variables['Build.Repository.Name'] }} - id: 7864 - comment: Nightly test job failed at commit $(Build.SourceVersion). View the logs at $(System.TeamFoundationCollectionUri)$(System.TeamProject)/_build/results?buildId=$(Build.BuildId). - - # Full PR suite. PRs need to pass this pipeline in order to be moved to the - # merge queue, where they'll use the next rule as the branch-protection rule - # for the final merge to the base branch. - - ${{ if eq(variables['Build.Reason'], 'PullRequest') }}: - # The preliminary stage should be small in both total runtime (including - # provisioning) and resources required. About half of PR commits result in - # a CI failure, and over 90% of these are in linting, documention or a test - # failure that would affect _any_ OS or Python version. The goal in the - # first stage is to catch the vast majority of failures with minimal cost. - - stage: "Lint_Docs_Prelim_Tests" - displayName: "Preliminary tests" - jobs: - - template: ".azure/lint_docs-linux.yml" - parameters: - pythonVersion: ${{ parameters.minimumPythonVersion }} - - - template: ".azure/test-linux.yml" - parameters: - pythonVersion: ${{ parameters.minimumPythonVersion }} - # A PR is more likely to fail CI because it introduces a logic error - # into an existing test than because it adds a new test / optional - # dependency that isn't accounted for in the test-skipping logic - # (and such a failure would need fewer iterations to fix). We want - # to fail fast in the first CI stage. - installOptionals: true - testRust: true - testImages: true - - # The rest of the PR pipeline is to test the oldest and newest supported - # versions of Python. It's very rare for a failure to be specific to an - # intermediate version of Python, so we just catch those in the cron-job - # pipeline to reduce the amount of resources used. - - stage: "Tests" - displayName: "Main tests" - dependsOn: "Lint_Docs_Prelim_Tests" - jobs: - - template: ".azure/test-linux.yml" - parameters: - pythonVersion: ${{ parameters.maximumPythonVersion }} - testRust: false - testImages: false - installFromSdist: true - installOptionals: false - - - template: ".azure/test-macos.yml" - parameters: - pythonVersion: ${{ parameters.minimumPythonVersion }} - installOptionals: true - - - template: ".azure/test-macos.yml" - parameters: - pythonVersion: ${{ parameters.maximumPythonVersion }} - installOptionals: false - - - template: ".azure/test-windows.yml" - parameters: - pythonVersion: ${{ parameters.minimumPythonVersion }} - installOptionals: true - - - template: ".azure/test-windows.yml" - parameters: - pythonVersion: ${{ parameters.maximumPythonVersion }} - installOptionals: false - - # Merge queue. A PR that reaches here has already passed the more rigorous PR - # suite, so is very likely to pass. The main reasons for failures here are - # flaky VMs timing out (which we can't do much about), or a merge conflict - # with another PR that is also in the merge queue. - # - # There's no reason to have multiple stages in this case, because we're - # expecting it to pass. Having more than one stage frustrates parallel - # throughput in low-contention cases, and guarantees a longer critical path. - - ${{ if and(eq(variables['Build.Reason'], 'IndividualCI'), contains(variables['Build.SourceBranch'], 'gh-readonly-queue')) }}: - - stage: "Merge_Queue" - displayName: "Merge queue" - jobs: - - template: ".azure/lint_docs-linux.yml" - parameters: - pythonVersion: ${{ parameters.minimumPythonVersion }} - - - template: ".azure/test-linux.yml" - parameters: - pythonVersion: ${{ parameters.minimumPythonVersion }} - installOptionals: true - testRust: true - testImages: true - - - template: ".azure/test-linux.yml" - parameters: - pythonVersion: ${{ parameters.maximumPythonVersion }} - installOptionals: false - testRust: false - testImages: false - - - template: ".azure/test-macos.yml" - parameters: - pythonVersion: ${{ parameters.maximumPythonVersion }} - installOptionals: false - - - template: ".azure/test-windows.yml" - parameters: - pythonVersion: ${{ parameters.maximumPythonVersion }} - installOptionals: false - - # Push to main or the stable branches. The triggering branches also need to - # be in the triggers at the top of this file. - - ${{ if and(eq(variables['Build.Reason'], 'IndividualCI'), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/main'), startsWith(variables['Build.SourceBranch'], 'refs/heads/stable/'))) }}: - - stage: "Push" - jobs: - - template: ".azure/test-linux.yml" - parameters: - pythonVersion: ${{ parameters.branchPushPythonVersion }} - testRust: true - testImages: true - installOptionals: false