diff --git a/test/qpy_compat/compare_versions.py b/test/qpy_compat/compare_versions.py deleted file mode 100755 index 5deec1b9ca5b..000000000000 --- a/test/qpy_compat/compare_versions.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python3 -# This code is part of Qiskit. -# -# (C) Copyright IBM 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Compare Qiskit versions to determine if we should run qpy compat tests.""" - -import argparse -import sys - -from qiskit.qpy.interface import VERSION_PATTERN_REGEX - - -def main(): - """Main function.""" - parser = argparse.ArgumentParser(prog="compare_version", description="Compare version strings") - parser.add_argument( - "source_version", help="Source version of Qiskit that is generating the payload" - ) - parser.add_argument("test_version", help="Version under test that will load the QPY payload") - args = parser.parse_args() - source_match = VERSION_PATTERN_REGEX.search(args.source_version) - target_match = VERSION_PATTERN_REGEX.search(args.test_version) - source_version = tuple(int(x) for x in source_match.group("release").split(".")) - target_version = tuple(int(x) for x in target_match.group("release").split(".")) - if source_version > target_version: - sys.exit(1) - sys.exit(0) - - -if __name__ == "__main__": - main() diff --git a/test/qpy_compat/get_versions.py b/test/qpy_compat/get_versions.py new file mode 100644 index 000000000000..04233524d12a --- /dev/null +++ b/test/qpy_compat/get_versions.py @@ -0,0 +1,74 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +import json +import urllib.request + +import packaging.version +import packaging.tags + +import qiskit + + +def tag_from_wheel_name(wheel: str) -> packaging.tags.Tag: + assert wheel.lowercase().endswith(".whl") + _prefix, interpreter, abi, platform = wheel[:-4].rsplit("-", 3) + tag = packaging.tags.Tag(interpreter, abi, platform) + return tag + + +def available_versions(): + """Get all the versions of Qiskit that support exporting QPY, and are installable with the + active version of Python on this platform.""" + our_version = packaging.version.parse(qiskit.__version__) + supported_tags = set(packaging.tags.sys_tags()) + + def available_versions_for_package(package, min_version=None, max_version=None): + with urllib.request.urlopen(f"https://pypi.org/pypi/{package}/json") as fd: + data = json.load(fd) + min_version = min_version and packaging.version.parse(min_version) + max_version = max_version and packaging.version.parse(max_version) + for other_version, payload in data["releases"].items(): + other_version = packaging.version.parse(other_version) + if min_version is not None and other_version < min_version: + continue + if max_version is not None and other_version >= max_version: + continue + if other_version > our_version or other_version.is_prerelease: + continue + # Note: this silently ignores versions that are uninstallable because we're using a + # Python version that's too new, which can be a problem for the oldest Terras, + # especially from before we built for abi3. + if not any( + tag_from_wheel_name(release["filename"]) in supported_tags + for release in payload + if release["packagetype"] == "bdist_wheel" + ): + continue + yield other_version + + yield from ( + ("qiskit-terra", version) + for version in available_versions_for_package("qiskit-terra", "0.18.0", "1.0.0") + ) + yield from ( + ("qiskit", version) for version in available_versions_for_package("qiskit", "1.0.0") + ) + + +def main(): + for package, version in available_versions(): + print(package, version) + + +if __name__ == "__main__": + main() diff --git a/test/qpy_compat/process_version.sh b/test/qpy_compat/process_version.sh index c3afcf3a3a5e..c56c44f554bb 100755 --- a/test/qpy_compat/process_version.sh +++ b/test/qpy_compat/process_version.sh @@ -14,56 +14,50 @@ set -e set -x -# version is the source version, this is the release with which to generate -# qpy files with to load with the version under test -version=$1 -parts=( ${version//./ } ) -# qiskit_version is the version under test, We're testing that we can correctly -# read the qpy files generated with source version with this version. -qiskit_version=$(./qiskit_venv/bin/python -c "import qiskit;print(qiskit.__version__)") -qiskit_parts=( ${qiskit_version//./ } ) +function usage { + echo "usage: ${BASH_SOURCE[0]} -p /path/to/qiskit/python " 1>&2 + exit 1 +} - -# If source version is less than 0.18 QPY didn't exist yet so exit fast -if [[ ${parts[0]} -eq 0 && ${parts[1]} -lt 18 ]] ; then - exit 0 +python="python" +while getopts "p:" opt; do + case "$opt" in + p) + python="$OPTARG" + ;; + *) + usage + ;; + esac +done +shift "$((OPTIND-1))" +if [[ $# != 2 ]]; then + usage fi -# Exclude any non-rc pre-releases as they don't have stable API guarantees -if [[ $version == *"b"* || $version == *"a"* ]] ; then - exit 0 -fi +# `package` is the name of the Python distribution to install (qiskit or qiskit-terra). `version` is +# the source version: the release with which to generate qpy files with to load with the version +# under test. +package="$1" +version="$2" -# If the source version is newer than the version under test exit fast because -# there is no QPY compatibility for loading a qpy file generated from a newer -# release with an older release of Qiskit. -if ! ./qiskit_venv/bin/python ./compare_versions.py "$version" "$qiskit_version" ; then - exit 0 -fi +our_dir="$(realpath -- "$(dirname -- "${BASH_SOURCE[0]}")")" +cache_dir="$(pwd -P)/qpy_$version" +venv_dir="$(pwd -P)/${version}" -if [[ ! -d qpy_$version ]] ; then - echo "Building venv for qiskit-terra $version" - python -m venv "$version" - if [[ ${parts[0]} -eq 0 ]] ; then - if ! "./$version/bin/pip" install -c qpy_test_constraints.txt "qiskit-terra==$version"; then - echo "Skipping uninstallable version $version"; - exit 0; - fi - else - if ! "./$version/bin/pip" install -c qpy_test_constraints.txt "qiskit==$version"; then - echo "Skipping uninstallable version $version"; - exit 0; - fi - fi - mkdir "qpy_$version" - pushd "qpy_$version" - echo "Generating qpy files with qiskit-terra $version" - "../$version/bin/python" ../test_qpy.py generate --version="$version" +if [[ ! -d $cache_dir ]] ; then + echo "Building venv for $package==$version" + "$python" -m venv "$venv_dir" + "$venv_dir/bin/pip" install -c "${our_dir}/qpy_test_constraints.txt" "${package}==${version}" + mkdir "$cache_dir" + pushd "$cache_dir" + echo "Generating QPY files with $package==$version" + "$venv_dir/bin/python" "${our_dir}/test_qpy.py" generate --version="$version" else echo "Using cached QPY files for $version" - pushd "qpy_$version" + pushd "${cache_dir}" fi -echo "Loading qpy files from $version with dev qiskit-terra" -../qiskit_venv/bin/python ../test_qpy.py load --version="$version" +echo "Loading qpy files from $version with dev Qiskit" +"$python" "${our_dir}/test_qpy.py" load --version="$version" popd -rm -rf "./$version" +rm -rf "$venv_dir" diff --git a/test/qpy_compat/run_tests.sh b/test/qpy_compat/run_tests.sh index 3810773ec0f3..d31f29e11638 100755 --- a/test/qpy_compat/run_tests.sh +++ b/test/qpy_compat/run_tests.sh @@ -12,6 +12,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. set -e +set -o pipefail set -x # Set fixed hash seed to ensure set orders are identical between saving and @@ -19,12 +20,16 @@ set -x export PYTHONHASHSEED=$(python -S -c "import random; print(random.randint(1, 4294967295))") echo "PYTHONHASHSEED=$PYTHONHASHSEED" -python -m venv qiskit_venv -qiskit_venv/bin/pip install -c ../../constraints.txt ../.. +our_dir="$(realpath -- "$(dirname -- "${BASH_SOURCE[0]}")")" -parallel bash ./process_version.sh ::: `git tag --sort=-creatordate` +qiskit_venv="$(pwd -P)/qiskit_venv" +qiskit_python="$qiskit_venv/bin/python" +python -m venv "$qiskit_venv" +"$qiskit_venv/bin/pip" install -c "$our_dir/../../constraints.txt" "$our_dir/../.." + +"$qiskit_python" "$our_dir/get_versions.py" | parallel -- bash "$our_dir/process_version.sh" -p "$qiskit_python" # Test dev compatibility -dev_version=`qiskit_venv/bin/python -c 'import qiskit;print(qiskit.__version__)'` -qiskit_venv/bin/python test_qpy.py generate --version=$dev_version -qiskit_venv/bin/python test_qpy.py load --version=$dev_version +dev_version="$("$qiskit_python" -c 'import qiskit; print(qiskit.__version__)')" +"$qiskit_python" "$our_dir/test_qpy.py" generate --version="$dev_version" +"$qiskit_python" "$our_dir/test_qpy.py" load --version="$dev_version"