Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use FakeProvider for jupyter and monitor tests #4296

Merged
merged 10 commits into from
May 15, 2020
14 changes: 0 additions & 14 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,27 +280,13 @@ 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
variable, `QISKIT_TESTS`:

Option | Description | Default
------ | ----------- | -------
`skip_online` | Skips tests that require remote requests. Does not require user credentials. | `False`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should also remove skip_online as an option from qiskit/test/testing_options.py.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't want to remove that or the decorator because other things (mainly the ibmq provider) for better or worse use things from qiskit.test directly: https://github.com/Qiskit/qiskit-ibmq-provider/blob/22e9b8d0930f1af0a7305229c7379909b3459cd3/test/decorators.py#L34

Ideally we should remove this dependency and not treat terra's test helpers (especially a terrible one that doesn't use env vars correctly) as an external api. But that feels like something outside the scope of this pr.

`run_slow` | It runs tests tagged as *slow*. | `False`

It is possible to provide more than one option separated with commas.
Expand Down
1 change: 1 addition & 0 deletions qiskit/test/mock/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"""

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
Expand Down
42 changes: 41 additions & 1 deletion qiskit/test/mock/fake_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -76,3 +76,43 @@ def __init__(self):
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
2 changes: 1 addition & 1 deletion qiskit/tools/jupyter/backend_overview.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
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


Expand Down
10 changes: 9 additions & 1 deletion qiskit/tools/jupyter/job_watcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion qiskit/tools/monitor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
44 changes: 24 additions & 20 deletions test/python/tools/jupyter/test_notebooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@

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.
Expand All @@ -33,14 +32,12 @@
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)
Expand All @@ -49,16 +46,26 @@ def _execute_notebook(self, filename, qe_token=None, qe_url=None):
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(
Expand All @@ -73,14 +80,11 @@ def test_jupyter_jobs_pbars(self):
'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__':
Expand Down
77 changes: 59 additions & 18 deletions test/python/tools/monitor/test_backend_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,43 +14,84 @@

"""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()
self.assertIn('Operational:', stdout)
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)

Expand Down