Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions test/test_integration_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
SerializableClassDecoder,
SerializableClass,
)
from .utils.utils import cancel_job_safe
from .mock.proxy_server import MockProxyServer, use_proxies


Expand Down Expand Up @@ -284,8 +285,8 @@ def test_cancel_job_queued(self, service):
_ = self._run_program(service, iterations=10, backend=real_device)
job = self._run_program(service, iterations=2, backend=real_device)
self._wait_for_status(job, JobStatus.QUEUED)
job.cancel()
self.assertEqual(job.status(), JobStatus.CANCELLED)
if not cancel_job_safe(job, self.log):
return
time.sleep(10) # Wait a bit for DB to update.
rjob = service.job(job.job_id)
self.assertEqual(rjob.status(), JobStatus.CANCELLED)
Expand All @@ -295,8 +296,8 @@ def test_cancel_job_running(self, service):
"""Test canceling a running job."""
job = self._run_program(service, iterations=3)
self._wait_for_status(job, JobStatus.RUNNING)
job.cancel()
self.assertEqual(job.status(), JobStatus.CANCELLED)
if not cancel_job_safe(job, self.log):
return
time.sleep(10) # Wait a bit for DB to update.
rjob = service.job(job.job_id)
self.assertEqual(rjob.status(), JobStatus.CANCELLED)
Expand Down Expand Up @@ -447,7 +448,7 @@ def result_callback(job_id, interim_result):
final_it = interim_result["iteration"]

final_it = 0
iterations = 3
iterations = 5
sub_tests = [JobStatus.QUEUED, JobStatus.RUNNING]

for status in sub_tests:
Expand All @@ -462,7 +463,8 @@ def result_callback(job_id, interim_result):
callback=result_callback,
)
self._wait_for_status(job, status)
job.cancel()
if not cancel_job_safe(job, self.log):
return
time.sleep(3) # Wait for cleanup
self.assertIsNotNone(job._ws_client._server_close_code)
self.assertLess(final_it, iterations)
Expand Down
88 changes: 21 additions & 67 deletions test/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@

import os
import logging
from typing import Optional

from qiskit import QuantumCircuit
from qiskit.qobj import QasmQobj
from qiskit.compiler import assemble, transpile
from qiskit.test.reference_circuits import ReferenceCircuits
from qiskit.pulse import Schedule
from qiskit.providers.jobstatus import JOB_FINAL_STATES, JobStatus
from qiskit_ibm_runtime.hub_group_project import HubGroupProject
from qiskit_ibm_runtime import IBMRuntimeService
from qiskit_ibm_runtime.ibm_backend import IBMBackend
from qiskit_ibm_runtime.runtime_job import RuntimeJob
from qiskit_ibm_runtime.exceptions import RuntimeInvalidStateError


def setup_test_logging(logger: logging.Logger, filename: str):
Expand Down Expand Up @@ -53,36 +51,6 @@ def setup_test_logging(logger: logging.Logger, filename: str):
logger.setLevel(os.getenv("LOG_LEVEL", "DEBUG"))


def most_busy_backend(
service: IBMRuntimeService,
hub: Optional[str] = None,
group: Optional[str] = None,
project: Optional[str] = None,
) -> IBMBackend:
"""Return the most busy backend for the provider given.

Return the most busy available backend for those that
have a `pending_jobs` in their `status`. Backends such as
local backends that do not have this are not considered.

Args:
service: IBM Quantum account provider.
hub: Name of the hub.
group: Name of the group.
project: Name of the project.

Returns:
The most busy backend.
"""
backends = service.backends(
simulator=False, operational=True, hub=hub, group=group, project=project
)
return max(
[b for b in backends if b.configuration().n_qubits >= 5],
key=lambda b: b.status().pending_jobs,
)


def get_large_circuit(backend: IBMBackend) -> QuantumCircuit:
"""Return a slightly larger circuit that would run a bit longer.

Expand All @@ -102,38 +70,6 @@ def get_large_circuit(backend: IBMBackend) -> QuantumCircuit:
return circuit


def bell_in_qobj(backend: IBMBackend, shots: int = 1024) -> QasmQobj:
"""Return a bell circuit in Qobj format.

Args:
backend: Backend to use for transpiling the circuit.
shots: Number of shots.

Returns:
A bell circuit in Qobj format.
"""
return assemble(
transpile(ReferenceCircuits.bell(), backend=backend),
backend=backend,
shots=shots,
)


def get_pulse_schedule(backend: IBMBackend) -> Schedule:
"""Return a pulse schedule."""
config = backend.configuration()
defaults = backend.defaults()
inst_map = defaults.instruction_schedule_map

# Run 2 experiments - 1 with x pulse and 1 without
x = inst_map.get("x", 0)
measure = inst_map.get("measure", range(config.n_qubits)) << x.duration
ground_sched = measure
excited_sched = x | measure
schedules = [ground_sched, excited_sched]
return schedules


def get_hgp(qe_token: str, qe_url: str, default: bool = True) -> HubGroupProject:
"""Return a HubGroupProject for the account.

Expand All @@ -159,3 +95,21 @@ def get_hgp(qe_token: str, qe_url: str, default: bool = True) -> HubGroupProject
hgp_to_return = hgp
break
return hgp_to_return


def cancel_job_safe(job: RuntimeJob, logger: logging.Logger) -> bool:
"""Cancel a runtime job."""
try:
job.cancel()
status = job.status()
assert (
status is JobStatus.CANCELLED
), "cancel() was successful for job {} but its " "status is {}.".format(
job.job_id, status
)
return True
except RuntimeInvalidStateError:
if job.status() in JOB_FINAL_STATES:
logger.warning("Unable to cancel job because it's already done.")
return False
raise