Use FakeProvider for jupyter and monitor tests (#4296)

* Use FakeProvider for jupyter and monitor tests

Right now the jupyter widget tests and backend monitor tests are the
only place in the qiskit-terra unittests that require talking to IQX.
This commit removes that dependency by leveraging the FakeProvider and
FakeBackends which contain snapshots of the responses from the IBMQ api.

To accomplish mocking out the qiskit-ibmq-provider usage the
backend_overview module had to be renamed because it conflicted with the
backend_overview function and was not importable via an absolute import.

* Fix tests and lint

* Remove online tests from contributing documentation

There are no longer any online tests in the suite now that the provider
interactions have been mocked out. This commit removes the documentation
about how to configure the online tests.

* Fixes from review comments
This commit is contained in:
Matthew Treinish 2020-05-15 19:26:34 -04:00 committed by GitHub
parent d4bbaf605c
commit a87fe61992
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 136 additions and 56 deletions

View File

@ -280,19 +280,6 @@ C:\..\> set LOG_LEVEL="INFO"
C:\..\> python -m unittest test/python/circuit/test_circuit_operations.py
```
##### Online Tests
Some tests require that you an IBMQ account configured. By default these
tests are always skipped. If you want to run these tests locally please
go to this
[page](https://quantumexperience.ng.bluemix.net/qx/account/advanced) and
register an account. Then you can either set the credentials explicitly
with the `IBMQ_TOKEN` and `IBMQ_URL` environment variables to specify
the token and url respectively for the IBMQ service. Alternatively, if
you already have a single set of credentials configured in your
environment (using a `.qiskitrc`) then you can just set
`QISKIT_TESTS_USE_CREDENTIALS_FILE` to `1` and it will use that.
##### Test Skip Options
How and which tests are executed is controlled by an environment
@ -300,7 +287,6 @@ variable, `QISKIT_TESTS`:
Option | Description | Default
------ | ----------- | -------
`skip_online` | Skips tests that require remote requests. Does not require user credentials. | `False`
`run_slow` | It runs tests tagged as *slow*. | `False`
It is possible to provide more than one option separated with commas.

View File

@ -23,6 +23,7 @@ The mock devices are mainly for testing the compiler.
"""
from .fake_provider import FakeProvider
from .fake_provider import FakeProviderFactory
from .fake_backend import FakeBackend
from .fake_job import FakeJob
from .fake_qobj import FakeQobj

View File

@ -12,7 +12,7 @@
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
# pylint: disable=wildcard-import
# pylint: disable=wildcard-import,unused-argument
"""
Fake provider class that provides access to fake backends.
@ -76,3 +76,43 @@ class FakeProvider(BaseProvider):
FakeAthens()]
super().__init__()
class FakeProviderFactory:
"""Fake provider factory class."""
def __init__(self):
self.fake_provider = FakeProvider()
def load_account(self):
"""Fake load_account method to mirror the IBMQ provider."""
pass
def enable_account(self, *args, **kwargs):
"""Fake enable_account method to mirror the IBMQ provider factory."""
pass
def disable_account(self):
"""Fake disable_account method to mirror the IBMQ provider factory."""
pass
def save_account(self, *args, **kwargs):
"""Fake save_account method to mirror the IBMQ provider factory."""
pass
@staticmethod
def delete_account():
"""Fake delete_account method to mirror the IBMQ provider factory."""
pass
def update_account(self, force=False):
"""Fake update_account method to mirror the IBMQ provider factory."""
pass
def providers(self):
"""Fake providers method to mirror the IBMQ provider."""
return [self.fake_provider]
def get_provider(self, hub=None, group=None, project=None):
"""Fake get_provider method to mirror the IBMQ provider."""
return self.fake_provider

View File

@ -22,7 +22,7 @@ from IPython.core.magic import line_magic, Magics, magics_class # pylint: disab
from IPython.core import magic_arguments # pylint: disable=import-error
import matplotlib.pyplot as plt # pylint: disable=import-error
import ipywidgets as widgets # pylint: disable=import-error
from qiskit.tools.monitor.backend_overview import get_unique_backends
from qiskit.tools.monitor.overview import get_unique_backends
from qiskit.visualization.gate_map import plot_gate_map

View File

@ -18,7 +18,11 @@
from IPython.core.magic import (line_magic, # pylint: disable=import-error
Magics, magics_class)
from qiskit.tools.events.pubsub import Subscriber
from qiskit.providers.ibmq.job.exceptions import IBMQJobApiError
try:
from qiskit.providers.ibmq.job.exceptions import IBMQJobApiError
HAS_IBMQ = True
except ImportError:
HAS_IBMQ = False
from .job_widgets import (build_job_viewer, make_clear_button,
make_labels, create_job_widget)
from .watcher_monitor import _job_monitor
@ -29,6 +33,10 @@ class JobWatcher(Subscriber):
"""
def __init__(self):
super().__init__()
if not HAS_IBMQ:
raise ImportError("qiskit-ibmq-provider is required to use the "
"job watcher. To install it run 'pip install "
"qiskit-ibmq-provider'")
self.jobs = []
self._init_subscriber()
self.job_viewer = None

View File

@ -16,4 +16,4 @@
"""
from .job_monitor import job_monitor
from .backend_overview import backend_monitor, backend_overview
from .overview import backend_monitor, backend_overview

View File

@ -22,9 +22,8 @@ import unittest
import nbformat
from nbconvert.preprocessors import ExecutePreprocessor
import qiskit
from qiskit.tools.visualization import HAS_MATPLOTLIB
from qiskit.test import (Path, QiskitTestCase, online_test, slow_test)
from qiskit.test import (Path, QiskitTestCase, slow_test)
# Timeout (in seconds) for a single notebook.
@ -33,14 +32,12 @@ TIMEOUT = 1000
JUPYTER_KERNEL = 'python3'
@unittest.skipUnless(hasattr(qiskit, 'IBMQ'),
'qiskit-ibmq-provider is required for these tests')
class TestJupyter(QiskitTestCase):
"""Notebooks test case."""
def setUp(self):
self.execution_path = os.path.join(Path.SDK.value, '..')
def _execute_notebook(self, filename, qe_token=None, qe_url=None):
def _execute_notebook(self, filename):
# Create the preprocessor.
execute_preprocessor = ExecutePreprocessor(timeout=TIMEOUT,
kernel_name=JUPYTER_KERNEL)
@ -49,16 +46,26 @@ class TestJupyter(QiskitTestCase):
with open(filename) as file_:
notebook = nbformat.read(file_, as_version=4)
if qe_token and qe_url:
top_str = "from qiskit import IBMQ\n"
top_str += "IBMQ.enable_account('{token}', '{url}')".format(token=qe_token,
url=qe_url)
top = nbformat.notebooknode.NotebookNode({'cell_type': 'code',
'execution_count': 0,
'metadata': {},
'outputs': [],
'source': top_str})
notebook.cells = [top] + notebook.cells
top_str = """
import qiskit
import sys
from unittest.mock import create_autospec, MagicMock
from qiskit.test.mock import FakeProviderFactory
from qiskit.providers import basicaer
fake_prov = FakeProviderFactory()
qiskit.IBMQ = fake_prov
ibmq_mock = create_autospec(basicaer)
ibmq_mock.IBMQJobApiError = MagicMock()
sys.modules['qiskit.providers.ibmq'] = ibmq_mock
sys.modules['qiskit.providers.ibmq.job'] = ibmq_mock
sys.modules['qiskit.providers.ibmq.job.exceptions'] = ibmq_mock
"""
top = nbformat.notebooknode.NotebookNode({'cell_type': 'code',
'execution_count': 0,
'metadata': {},
'outputs': [],
'source': top_str})
notebook.cells = [top] + notebook.cells
# Run the notebook into the folder containing the `qiskit/` module.
execute_preprocessor.preprocess(
@ -73,14 +80,11 @@ class TestJupyter(QiskitTestCase):
'notebooks/test_pbar_status.ipynb'))
@unittest.skipIf(not HAS_MATPLOTLIB, 'matplotlib not available.')
@online_test
@slow_test
def test_backend_tools(self, qe_token, qe_url):
def test_backend_tools(self):
"""Test Jupyter backend tools."""
self._execute_notebook(self._get_resource_path(
'notebooks/test_backend_tools.ipynb'),
qe_token=qe_token,
qe_url=qe_url)
'notebooks/test_backend_tools.ipynb'))
if __name__ == '__main__':

View File

@ -14,24 +14,69 @@
"""Tests for the wrapper functionality."""
import sys
import unittest
from unittest.mock import patch
from unittest.mock import MagicMock
from io import StringIO
import qiskit
from qiskit import providers
from qiskit.tools.monitor import backend_overview, backend_monitor
from qiskit.test import QiskitTestCase, online_test
from qiskit.test import QiskitTestCase
from qiskit.test.mock import FakeProviderFactory
from qiskit.test.mock import FakeBackend
from qiskit.test.mock import FakeVigo
class TestBackendOverview(QiskitTestCase):
"""Tools test case."""
@online_test
def test_backend_overview(self, qe_token, qe_url):
"""Test backend_overview"""
from qiskit import IBMQ # pylint: disable: import-error
IBMQ.enable_account(qe_token, qe_url)
self.addCleanup(IBMQ.disable_account)
def _restore_ibmq(self):
if not self.import_error:
qiskit.IBMQ = self.ibmq_back
else:
del qiskit.IBMQ
if self.prov_backup:
providers.ibmq = self.prov_backup
else:
del providers.ibmq
def _restore_ibmq_mod(self):
if self.ibmq_module_backup is not None:
sys.modules['qiskit.providers.ibmq'] = self.ibmq_module_backup
else:
sys.modules.pop('qiskit.providers.ibmq')
def setUp(self):
super().setUp()
ibmq_mock = MagicMock()
ibmq_mock.IBMQBackend = FakeBackend
if 'qiskit.providers.ibmq' in sys.modules:
self.ibmq_module_backup = sys.modules['qiskit.providers.ibmq']
else:
self.ibmq_module_backup = None
sys.modules['qiskit.providers.ibmq'] = ibmq_mock
self.addCleanup(self._restore_ibmq_mod)
if hasattr(qiskit, 'IBMQ'):
self.import_error = False
else:
self.import_error = True
qiskit.IBMQ = None
self.ibmq_back = qiskit.IBMQ
qiskit.IBMQ = FakeProviderFactory()
self.addCleanup(self._restore_ibmq)
if hasattr(providers, 'ibmq'):
self.prov_backup = providers.ibmq
else:
self.prov_backup = None
providers.ibmq = MagicMock()
@patch('qiskit.tools.monitor.overview.get_unique_backends',
return_value=[FakeVigo()])
def test_backend_overview(self, _):
"""Test backend_overview"""
with patch('sys.stdout', new=StringIO()) as fake_stdout:
backend_overview()
stdout = fake_stdout.getvalue()
@ -39,18 +84,14 @@ class TestBackendOverview(QiskitTestCase):
self.assertIn('Avg. T1:', stdout)
self.assertIn('Num. Qubits:', stdout)
@online_test
def test_backend_monitor(self, qe_token, qe_url):
@patch('qiskit.tools.monitor.overview.get_unique_backends',
return_value=[FakeVigo()])
def test_backend_monitor(self, _):
"""Test backend_monitor"""
from qiskit import IBMQ # pylint: disable: import-error
IBMQ.enable_account(qe_token, qe_url)
self.addCleanup(IBMQ.disable_account)
for provider in IBMQ.providers():
for back in provider.backends():
if not back.configuration().simulator:
backend = back
break
for back in [FakeVigo()]:
if not back.configuration().simulator:
backend = back
break
with patch('sys.stdout', new=StringIO()) as fake_stdout:
backend_monitor(backend)