diff --git a/build.py b/build.py index d5a06d3c0c..a721f0ad36 100755 --- a/build.py +++ b/build.py @@ -514,6 +514,39 @@ def run_ci_historic_benchmark(): build_wheel(python_bin, pip_src, env=pip_env) step_end() + if run_tests: + # The qsharp shim tests require qdk (with its native extension) to be + # installed. If qdk was built in this run it is already available; + # otherwise check the environment. + qdk_available = build_qdk + if not qdk_available: + python_bin_check, _ = use_python_env(qdk_python_src) + result = subprocess.run( + [python_bin_check, "-c", "import qdk"], + capture_output=True, + ) + qdk_available = result.returncode == 0 + + if qdk_available: + step_start("Running tests for the qsharp compatibility shim") + # Use the qdk environment where qdk and test deps are already installed. + python_bin, pip_env = use_python_env(qdk_python_src) + + if not args.editable: + install_from_wheels(python_bin, "qsharp", cwd=pip_src) + + run_python_tests( + os.path.join(pip_src, "tests"), + python_bin, + pip_env, + ) + step_end() + else: + print( + "Skipping qsharp shim tests: qdk is not installed. " + "Run with --qdk or install qdk first." + ) + if build_widgets: step_start("Building the Python widgets") diff --git a/source/pip/tests/test_qsharp_shim.py b/source/pip/tests/test_qsharp_shim.py new file mode 100644 index 0000000000..674ae0e48b --- /dev/null +++ b/source/pip/tests/test_qsharp_shim.py @@ -0,0 +1,93 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +"""Tests for the ``qsharp`` compatibility shim. + +The ``qsharp`` package is a thin deprecation wrapper that re-exports the +public API from ``qdk``. These tests verify that the expected symbols and +submodules are accessible via ``import qsharp``. +""" + +import importlib +import warnings + +import pytest + +# ---- Symbols that must be directly accessible on ``qsharp`` ---- + +_EXPECTED_SYMBOLS = [ + # functions + "init", + "eval", + "run", + "compile", + "circuit", + "estimate", + "estimate_custom", + "logical_counts", + "set_quantum_seed", + "set_classical_seed", + "dump_machine", + "dump_circuit", + # types / classes + "Result", + "Pauli", + "QSharpError", + "TargetProfile", + "StateDump", + "ShotResult", + "PauliNoise", + "DepolarizingNoise", + "BitFlipNoise", + "PhaseFlipNoise", + "CircuitGenerationMethod", +] + +# Submodules that must be importable *and* accessible as attributes +# on the ``qsharp`` module after a bare ``import qsharp``. +_EXPECTED_SUBMODULES = [ + "code", + "estimator", +] + + +@pytest.fixture() +def qsharp_module(): + """Import the ``qsharp`` shim while suppressing the deprecation warning.""" + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + mod = importlib.import_module("qsharp") + return mod + + +def test_deprecation_warning_on_import(): + """Importing ``qsharp`` must emit a DeprecationWarning.""" + with warnings.catch_warnings(record=True) as caught: + warnings.simplefilter("always") + importlib.reload(importlib.import_module("qsharp")) + + deprecations = [w for w in caught if issubclass(w.category, DeprecationWarning)] + assert any("qsharp" in str(w.message) for w in deprecations) + + +@pytest.mark.parametrize("name", _EXPECTED_SYMBOLS) +def test_symbol_accessible(qsharp_module, name): + """Every expected symbol must be an attribute of ``qsharp``.""" + assert hasattr(qsharp_module, name), f"qsharp.{name} is missing" + + +@pytest.mark.parametrize("name", _EXPECTED_SUBMODULES) +def test_submodule_accessible_as_attribute(qsharp_module, name): + """Submodules like ``code`` must be reachable via ``qsharp.`` + without a separate ``from qsharp import ``.""" + attr = getattr(qsharp_module, name, None) + assert attr is not None, f"qsharp.{name} is not accessible as an attribute" + + +@pytest.mark.parametrize("name", _EXPECTED_SUBMODULES) +def test_submodule_importable(name): + """``from qsharp import `` must work.""" + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + mod = importlib.import_module(f"qsharp.{name}") + assert mod is not None