Move QiskitTestCase to qiskit.test (#1616)

* Move JobTestCase to test.python.ibmq

* Move common testing functionality to qiskit.test

Temporary commit for moving the files to qiskit.test.

* Split qiskit.test.common into separate modules

* Style and docstring adjustments

* Add new Path.QASMS, revise existing ones

* Update CHANGELOG
This commit is contained in:
Diego M. Rodríguez 2018-12-28 15:09:14 +01:00 committed by Jay Gambetta
parent eea013d14b
commit 8245a11be4
22 changed files with 475 additions and 429 deletions

View File

@ -115,7 +115,8 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme
# pi = the PI constant
# op = operation iterator
# b = basis iterator
good-names=i,j,k,n,m,ex,v,w,x,y,z,Run,_,logger,q,r,qr,cr,qc,pi,op,b,ar,br
good-names=i,j,k,n,m,ex,v,w,x,y,z,Run,_,logger,q,r,qr,cr,qc,pi,op,b,ar,br,
__unittest
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,toto,tutu,tata

View File

@ -25,6 +25,8 @@ Changed
- The ``Exception`` subclasses have been moved to an ``.exceptions`` module
within each package (for example, ``qiskit.exceptions.QiskitError``). (#1600).
- The ``QiskitTestCase`` and testing utilities are now included as part of
``qiskit.test`` and thus available for third-party implementations. (#1616).
Removed
-------

12
qiskit/test/__init__.py Normal file
View File

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
# Copyright 2018, IBM.
#
# This source code is licensed under the Apache License, Version 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.
"""Functionality and helpers for testing Qiskit."""
from .base import QiskitTestCase
from .decorators import requires_cpp_simulator, requires_qe_access, slow_test
from .utils import Path

123
qiskit/test/base.py Normal file
View File

@ -0,0 +1,123 @@
# -*- coding: utf-8 -*-
# Copyright 2018, IBM.
#
# This source code is licensed under the Apache License, Version 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.
"""Base TestCases for the unit tests.
Implementors of unit tests for Terra are encouraged to subclass
``QiskitTestCase`` in order to take advantage of utility functions (for example,
the environment variables for customizing different options), and the
decorators in the ``decorators`` package.
"""
import inspect
import logging
import os
import unittest
from unittest.util import safe_repr
from .utils import Path, _AssertNoLogsContext, setup_test_logging
__unittest = True # Allows shorter stack trace for .assertDictAlmostEqual
class QiskitTestCase(unittest.TestCase):
"""Helper class that contains common functionality."""
@classmethod
def setUpClass(cls):
# Determines if the TestCase is using IBMQ credentials.
cls.using_ibmq_credentials = False
# Set logging to file and stdout if the LOG_LEVEL envar is set.
cls.log = logging.getLogger(cls.__name__)
if os.getenv('LOG_LEVEL'):
filename = '%s.log' % os.path.splitext(inspect.getfile(cls))[0]
setup_test_logging(cls.log, os.getenv('LOG_LEVEL'), filename)
def tearDown(self):
# Reset the default providers, as in practice they acts as a singleton
# due to importing the wrapper from qiskit.
from qiskit.providers.ibmq import IBMQ
from qiskit.providers.builtinsimulators import BasicAer
IBMQ._accounts.clear()
BasicAer._backends = BasicAer._verify_backends()
@staticmethod
def _get_resource_path(filename, path=Path.TEST):
"""Get the absolute path to a resource.
Args:
filename (string): filename or relative path to the resource.
path (Path): path used as relative to the filename.
Returns:
str: the absolute path to the resource.
"""
return os.path.normpath(os.path.join(path.value, filename))
def assertNoLogs(self, logger=None, level=None):
"""Assert that no message is sent to the specified logger and level.
Context manager to test that no message is sent to the specified
logger and level (the opposite of TestCase.assertLogs()).
"""
return _AssertNoLogsContext(self, logger, level)
def assertDictAlmostEqual(self, dict1, dict2, delta=None, msg=None,
places=None, default_value=0):
"""Assert two dictionaries with numeric values are almost equal.
Fail if the two dictionaries are unequal as determined by
comparing that the difference between values with the same key are
not greater than delta (default 1e-8), or that difference rounded
to the given number of decimal places is not zero. If a key in one
dictionary is not in the other the default_value keyword argument
will be used for the missing value (default 0). If the two objects
compare equal then they will automatically compare almost equal.
Args:
dict1 (dict): a dictionary.
dict2 (dict): a dictionary.
delta (number): threshold for comparison (defaults to 1e-8).
msg (str): return a custom message on failure.
places (int): number of decimal places for comparison.
default_value (number): default value for missing keys.
Raises:
TypeError: raises TestCase failureException if the test fails.
"""
def valid_comparison(value):
if places is not None:
return round(value, places) == 0
else:
return value < delta
# Check arguments.
if dict1 == dict2:
return
if places is not None:
if delta is not None:
raise TypeError("specify delta or places not both")
msg_suffix = ' within %s places' % places
else:
delta = delta or 1e-8
msg_suffix = ' within %s delta' % delta
# Compare all keys in both dicts, populating error_msg.
error_msg = ''
for key in set(dict1.keys()) | set(dict2.keys()):
val1 = dict1.get(key, default_value)
val2 = dict2.get(key, default_value)
if not valid_comparison(abs(val1 - val2)):
error_msg += '(%s: %s != %s), ' % (safe_repr(key),
safe_repr(val1),
safe_repr(val2))
if error_msg:
msg = self._formatMessage(msg, error_msg[:-2] + msg_suffix)
raise self.failureException(msg)

164
qiskit/test/decorators.py Normal file
View File

@ -0,0 +1,164 @@
# -*- coding: utf-8 -*-
# Copyright 2018, IBM.
#
# This source code is licensed under the Apache License, Version 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.
"""Decorator for using with Qiskit unit tests."""
import functools
import os
import unittest
from qiskit.providers.ibmq.credentials import Credentials, discover_credentials
from qiskit.providers.legacysimulators import QasmSimulator
from .utils import Path
from .http_recorder import http_recorder
from .testing_options import get_test_options
def is_cpp_simulator_available():
"""Check if the C++ simulator can be instantiated.
Returns:
bool: True if simulator executable is available
"""
try:
QasmSimulator()
except FileNotFoundError:
return False
return True
def requires_cpp_simulator(test_item):
"""Decorator that skips test if C++ simulator is not available
Args:
test_item (callable): function or class to be decorated.
Returns:
callable: the decorated function.
"""
reason = 'C++ simulator not found, skipping test'
return unittest.skipIf(not is_cpp_simulator_available(), reason)(test_item)
def slow_test(func):
"""Decorator that signals that the test takes minutes to run.
Args:
func (callable): test function to be decorated.
Returns:
callable: the decorated function.
"""
@functools.wraps(func)
def _wrapper(*args, **kwargs):
skip_slow = not TEST_OPTIONS['run_slow']
if skip_slow:
raise unittest.SkipTest('Skipping slow tests')
return func(*args, **kwargs)
return _wrapper
def _get_credentials(test_object, test_options):
"""Finds the credentials for a specific test and options.
Args:
test_object (QiskitTestCase): The test object asking for credentials
test_options (dict): Options after QISKIT_TESTS was parsed by get_test_options.
Returns:
Credentials: set of credentials
Raises:
Exception: When the credential could not be set and they are needed for that set of options
"""
dummy_credentials = Credentials('dummyapiusersloginWithTokenid01',
'https://quantumexperience.ng.bluemix.net/api')
if test_options['mock_online']:
return dummy_credentials
if os.getenv('USE_ALTERNATE_ENV_CREDENTIALS', ''):
# Special case: instead of using the standard credentials mechanism,
# load them from different environment variables. This assumes they
# will always be in place, as is used by the Travis setup.
return Credentials(os.getenv('IBMQ_TOKEN'), os.getenv('IBMQ_URL'))
else:
# Attempt to read the standard credentials.
discovered_credentials = discover_credentials()
if discovered_credentials:
# Decide which credentials to use for testing.
if len(discovered_credentials) > 1:
try:
# Attempt to use QE credentials.
return discovered_credentials[dummy_credentials.unique_id()]
except KeyError:
pass
# Use the first available credentials.
return list(discovered_credentials.values())[0]
# No user credentials were found.
if test_options['rec']:
raise Exception('Could not locate valid credentials. You need them for recording '
'tests against the remote API.')
test_object.log.warning("No user credentials were detected. Running with mocked data.")
test_options['mock_online'] = True
return dummy_credentials
def requires_qe_access(func):
"""Decorator that signals that the test uses the online API:
It involves:
* determines if the test should be skipped by checking environment
variables.
* if the `USE_ALTERNATE_ENV_CREDENTIALS` environment variable is
set, it reads the credentials from an alternative set of environment
variables.
* if the test is not skipped, it reads `qe_token` and `qe_url` from
`Qconfig.py`, environment variables or qiskitrc.
* if the test is not skipped, it appends `qe_token` and `qe_url` as
arguments to the test function.
Args:
func (callable): test function to be decorated.
Returns:
callable: the decorated function.
"""
@functools.wraps(func)
def _wrapper(self, *args, **kwargs):
if TEST_OPTIONS['skip_online']:
raise unittest.SkipTest('Skipping online tests')
credentials = _get_credentials(self, TEST_OPTIONS)
self.using_ibmq_credentials = credentials.is_ibmq()
kwargs.update({'qe_token': credentials.token,
'qe_url': credentials.url})
decorated_func = func
if TEST_OPTIONS['rec'] or TEST_OPTIONS['mock_online']:
# For recording or for replaying existing cassettes, the test
# should be decorated with @use_cassette.
vcr_mode = 'new_episodes' if TEST_OPTIONS['rec'] else 'none'
decorated_func = http_recorder(
vcr_mode, Path.CASSETTES.value).use_cassette()(decorated_func)
return decorated_func(self, *args, **kwargs)
return _wrapper
TEST_OPTIONS = get_test_options()

View File

@ -14,7 +14,8 @@ from vcr import VCR
class IdRemoverPersister(FilesystemPersister):
"""
"""VCR Persister for Qiskit.
IdRemoverPersister is a VCR persister. This is, it implements a way to save and load cassettes.
This persister in particular inherits load_cassette from FilesystemPersister (basically, it
loads a standard cassette in the standard way from the file system). On the saving side, it
@ -23,8 +24,7 @@ class IdRemoverPersister(FilesystemPersister):
@staticmethod
def get_responses_with(string_to_find, cassette_dict):
"""
Filters the requests from cassette_dict
"""Filters the requests from cassette_dict
Args:
string_to_find (str): request path
@ -39,8 +39,7 @@ class IdRemoverPersister(FilesystemPersister):
@staticmethod
def get_new_id(field, path, id_tracker, type_=str):
"""
Creates a new dummy id (or value) for replacing an existing id (or value).
"""Creates a new dummy id (or value) for replacing an existing id (or value).
Args:
field (str): field name is used, in same cases, to create a dummy value.
@ -62,8 +61,8 @@ class IdRemoverPersister(FilesystemPersister):
@staticmethod
def get_matching_dicts(data_dict, map_list):
"""
Find subdicts that are described in map_list.
"""Find subdicts that are described in map_list.
Args:
data_dict (dict): in which the map_list is going to be searched.
map_list (list): the list of nested keys to find in the data_dict
@ -88,7 +87,8 @@ class IdRemoverPersister(FilesystemPersister):
@staticmethod
def remove_id_in_a_json(jsonobj, field, path, id_tracker):
"""
"""Replaces ids with dummy values in a json.
Replaces in jsonobj (in-place) the field with dummy value (which is constructed with
id_tracker, if it was already replaced, or path, if it needs to be created).
@ -110,7 +110,8 @@ class IdRemoverPersister(FilesystemPersister):
@staticmethod
def remove_ids_in_a_response(response, fields, path, id_tracker):
"""
"""Replaces ids with dummy values in a response.
Replaces in response (in-place) the fields with dummy values (which is constructed with
id_tracker, if it was already replaced, or path, if it needs to be created).
@ -127,7 +128,8 @@ class IdRemoverPersister(FilesystemPersister):
@staticmethod
def remove_ids(ids2remove, cassette_dict):
"""
"""Replaces ids with dummy values in a cassette.
Replaces in cassette_dict (in-place) the fields defined by ids2remove with dummy values.
Internally, it used a map (id_tracker) between real values and dummy values to keep
consistency during the renaming.
@ -149,7 +151,8 @@ class IdRemoverPersister(FilesystemPersister):
@staticmethod
def save_cassette(cassette_path, cassette_dict, serializer):
"""
"""Extends FilesystemPersister.save_cassette
Extends FilesystemPersister.save_cassette. Replaces particular values (defined by
ids2remove) which are replaced by a dummy value. The full manipulation is in
cassette_dict, before saving it using FilesystemPersister.save_cassette
@ -181,8 +184,7 @@ class IdRemoverPersister(FilesystemPersister):
def http_recorder(vcr_mode, cassette_dir):
"""
Creates a VCR object in vcr_mode mode.
"""Creates a VCR object in vcr_mode mode.
Args:
vcr_mode (string): the parameter for record_mode.
@ -213,8 +215,7 @@ def http_recorder(vcr_mode, cassette_dir):
def _purge_headers_cb(headers):
"""
Remove headers from the response.
"""Remove headers from the response.
Args:
headers (list): headers to remove from the response
@ -222,7 +223,6 @@ def _purge_headers_cb(headers):
Returns:
callable: for been used in before_record_response VCR constructor.
"""
header_list = []
for item in headers:
if not isinstance(item, tuple):
@ -230,8 +230,7 @@ def _purge_headers_cb(headers):
header_list.append(item[0:2]) # ensure the tuple is a pair
def before_record_response_cb(response):
"""
Purge headers from response.
"""Purge headers from response.
Args:
response (dict): a VCR response
@ -251,7 +250,8 @@ def _purge_headers_cb(headers):
def _unordered_query_matcher(request1, request2):
"""
"""A VCR matcher that ignores the order of values in the query string.
A VCR matcher (a la VCR.matcher) that ignores the order of the values in the query string.
Useful for filter params, for example.

View File

@ -30,8 +30,8 @@ def get_test_options(option_var='QISKIT_TESTS'):
}
def turn_false(option):
"""
Turn an option to False
"""Turn an option to False.
Args:
option (str): Turns defaults[option] to False
@ -49,8 +49,7 @@ def get_test_options(option_var='QISKIT_TESTS'):
}
def set_flag(flag_):
"""
Set the flag to True and flip all the flags that need to be rewritten.
"""Set the flag to True and flip all the flags that need to be rewritten.
Args:
flag_ (str): Option to be True
@ -74,7 +73,8 @@ def get_test_options(option_var='QISKIT_TESTS'):
def _is_ci_fork_pull_request():
"""
"""Check if the tests are being run in a CI environment from a PR.
Check if the tests are being run in a CI environment and if it is a pull
request.

82
qiskit/test/utils.py Normal file
View File

@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
# Copyright 2018, IBM.
#
# This source code is licensed under the Apache License, Version 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.
"""Utils for using with Qiskit unit tests."""
import logging
import os
import unittest
from enum import Enum
from qiskit import __path__ as qiskit_path
class Path(Enum):
"""Helper with paths commonly used during the tests."""
# Main SDK path: qiskit/
SDK = qiskit_path[0]
# test.python path: qiskit/test/python/
TEST = os.path.normpath(os.path.join(SDK, '..', 'test', 'python'))
# Examples path: examples/
EXAMPLES = os.path.normpath(os.path.join(SDK, '..', 'examples'))
# Schemas path: qiskit/schemas
SCHEMAS = os.path.normpath(os.path.join(SDK, 'schemas'))
# VCR cassettes path: qiskit/test/cassettes/
CASSETTES = os.path.normpath(os.path.join(TEST, '..', 'cassettes'))
# Sample QASMs path: qiskit/test/python/qasm
QASMS = os.path.normpath(os.path.join(TEST, 'qasm'))
def setup_test_logging(logger, log_level, filename):
"""Set logging to file and stdout for a logger.
Args:
logger (Logger): logger object to be updated.
log_level (str): logging level.
filename (str): name of the output file.
"""
# Set up formatter.
log_fmt = ('{}.%(funcName)s:%(levelname)s:%(asctime)s:'
' %(message)s'.format(logger.name))
formatter = logging.Formatter(log_fmt)
# Set up the file handler.
file_handler = logging.FileHandler(filename)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
# Set the logging level from the environment variable, defaulting
# to INFO if it is not a valid level.
level = logging._nameToLevel.get(log_level, logging.INFO)
logger.setLevel(level)
class _AssertNoLogsContext(unittest.case._AssertLogsContext):
"""A context manager used to implement TestCase.assertNoLogs()."""
# pylint: disable=inconsistent-return-statements
def __exit__(self, exc_type, exc_value, tb):
"""
This is a modified version of TestCase._AssertLogsContext.__exit__(...)
"""
self.logger.handlers = self.old_handlers
self.logger.propagate = self.old_propagate
self.logger.setLevel(self.old_level)
if exc_type is not None:
# let unexpected exceptions pass through
return False
if self.watcher.records:
msg = 'logs of level {} or higher triggered on {}:\n'.format(
logging.getLevelName(self.level), self.logger.name)
for record in self.watcher.records:
msg += 'logger %s %s:%i: %s\n' % (record.name, record.pathname,
record.lineno,
record.getMessage())
self._raiseFailure(msg)

View File

@ -120,7 +120,7 @@ class LoadFromQasmTest(QiskitTestCase):
def test_qasm_example_file(self):
"""Loads qasm/example.qasm.
"""
qasm_filename = self._get_resource_path('qasm/example.qasm')
qasm_filename = self._get_resource_path('example.qasm', Path.QASMS)
expected_circuit = QuantumCircuit.from_qasm_str('\n'.join(["OPENQASM 2.0;",
"include \"qelib1.inc\";",
"qreg q[3];",

View File

@ -7,384 +7,12 @@
"""Shared functionality and helpers for the unit tests."""
from enum import Enum
import functools
import inspect
import logging
import os
import time
import unittest
from unittest.util import safe_repr
from qiskit import __path__ as qiskit_path
from qiskit.providers import JobStatus
from qiskit.providers.legacysimulators import QasmSimulator
from qiskit.providers.ibmq.credentials import discover_credentials, Credentials
# pylint: disable=unused-import
from .http_recorder import http_recorder
from ._test_options import get_test_options
# TODO: once all the tests in test/python import from qiskit.test, this file
# can be safely removed.
# Allows shorter stack trace for .assertDictAlmostEqual
__unittest = True # pylint: disable=invalid-name
class Path(Enum):
"""Helper with paths commonly used during the tests."""
# Main SDK path: qiskit/
SDK = qiskit_path[0]
# test.python path: qiskit/test/python/
TEST = os.path.dirname(__file__)
# Examples path: examples/
EXAMPLES = os.path.join(SDK, '..', 'examples')
# Schemas path: qiskit/schemas
SCHEMAS = os.path.join(SDK, 'schemas')
# VCR cassettes path: qiskit/test/cassettes/
CASSETTES = os.path.join(TEST, '..', 'cassettes')
class QiskitTestCase(unittest.TestCase):
"""Helper class that contains common functionality."""
@classmethod
def setUpClass(cls):
cls.moduleName = os.path.splitext(inspect.getfile(cls))[0]
cls.log = logging.getLogger(cls.__name__)
# Determines if the TestCase is using IBMQ credentials.
cls.using_ibmq_credentials = False
# Set logging to file and stdout if the LOG_LEVEL environment variable
# is set.
if os.getenv('LOG_LEVEL'):
# Set up formatter.
log_fmt = ('{}.%(funcName)s:%(levelname)s:%(asctime)s:'
' %(message)s'.format(cls.__name__))
formatter = logging.Formatter(log_fmt)
# Set up the file handler.
log_file_name = '%s.log' % cls.moduleName
file_handler = logging.FileHandler(log_file_name)
file_handler.setFormatter(formatter)
cls.log.addHandler(file_handler)
# Set the logging level from the environment variable, defaulting
# to INFO if it is not a valid level.
level = logging._nameToLevel.get(os.getenv('LOG_LEVEL'),
logging.INFO)
cls.log.setLevel(level)
cls.log.debug("QISKIT_TESTS: %s", str(TEST_OPTIONS))
def tearDown(self):
# Reset the default providers, as in practice they acts as a singleton
# due to importing the wrapper from qiskit.
from qiskit.providers.ibmq import IBMQ
from qiskit.providers.builtinsimulators import BasicAer
IBMQ._accounts.clear()
BasicAer._backends = BasicAer._verify_backends()
@staticmethod
def _get_resource_path(filename, path=Path.TEST):
""" Get the absolute path to a resource.
Args:
filename (string): filename or relative path to the resource.
path (Path): path used as relative to the filename.
Returns:
str: the absolute path to the resource.
"""
return os.path.normpath(os.path.join(path.value, filename))
def assertNoLogs(self, logger=None, level=None):
"""
Context manager to test that no message is sent to the specified
logger and level (the opposite of TestCase.assertLogs()).
"""
return _AssertNoLogsContext(self, logger, level)
def assertDictAlmostEqual(self, dict1, dict2, delta=None, msg=None,
places=None, default_value=0):
"""
Assert two dictionaries with numeric values are almost equal.
Fail if the two dictionaries are unequal as determined by
comparing that the difference between values with the same key are
not greater than delta (default 1e-8), or that difference rounded
to the given number of decimal places is not zero. If a key in one
dictionary is not in the other the default_value keyword argument
will be used for the missing value (default 0). If the two objects
compare equal then they will automatically compare almost equal.
Args:
dict1 (dict): a dictionary.
dict2 (dict): a dictionary.
delta (number): threshold for comparison (defaults to 1e-8).
msg (str): return a custom message on failure.
places (int): number of decimal places for comparison.
default_value (number): default value for missing keys.
Raises:
TypeError: raises TestCase failureException if the test fails.
"""
if dict1 == dict2:
# Shortcut
return
if delta is not None and places is not None:
raise TypeError("specify delta or places not both")
if places is not None:
success = True
standard_msg = ''
# check value for keys in target
keys1 = set(dict1.keys())
for key in keys1:
val1 = dict1.get(key, default_value)
val2 = dict2.get(key, default_value)
if round(abs(val1 - val2), places) != 0:
success = False
standard_msg += '(%s: %s != %s), ' % (safe_repr(key),
safe_repr(val1),
safe_repr(val2))
# check values for keys in counts, not in target
keys2 = set(dict2.keys()) - keys1
for key in keys2:
val1 = dict1.get(key, default_value)
val2 = dict2.get(key, default_value)
if round(abs(val1 - val2), places) != 0:
success = False
standard_msg += '(%s: %s != %s), ' % (safe_repr(key),
safe_repr(val1),
safe_repr(val2))
if success is True:
return
standard_msg = standard_msg[:-2] + ' within %s places' % places
else:
if delta is None:
delta = 1e-8 # default delta value
success = True
standard_msg = ''
# check value for keys in target
keys1 = set(dict1.keys())
for key in keys1:
val1 = dict1.get(key, default_value)
val2 = dict2.get(key, default_value)
if abs(val1 - val2) > delta:
success = False
standard_msg += '(%s: %s != %s), ' % (safe_repr(key),
safe_repr(val1),
safe_repr(val2))
# check values for keys in counts, not in target
keys2 = set(dict2.keys()) - keys1
for key in keys2:
val1 = dict1.get(key, default_value)
val2 = dict2.get(key, default_value)
if abs(val1 - val2) > delta:
success = False
standard_msg += '(%s: %s != %s), ' % (safe_repr(key),
safe_repr(val1),
safe_repr(val2))
if success is True:
return
standard_msg = standard_msg[:-2] + ' within %s delta' % delta
msg = self._formatMessage(msg, standard_msg)
raise self.failureException(msg)
class JobTestCase(QiskitTestCase):
"""Include common functionality when testing jobs."""
def wait_for_initialization(self, job, timeout=1):
"""Waits until the job progress from `INITIALIZING` to a different
status.
"""
waited = 0
wait = 0.1
while job.status() is JobStatus.INITIALIZING:
time.sleep(wait)
waited += wait
if waited > timeout:
self.fail(
msg="The JOB is still initializing after timeout ({}s)"
.format(timeout)
)
class _AssertNoLogsContext(unittest.case._AssertLogsContext):
"""A context manager used to implement TestCase.assertNoLogs()."""
# pylint: disable=inconsistent-return-statements
def __exit__(self, exc_type, exc_value, tb):
"""
This is a modified version of TestCase._AssertLogsContext.__exit__(...)
"""
self.logger.handlers = self.old_handlers
self.logger.propagate = self.old_propagate
self.logger.setLevel(self.old_level)
if exc_type is not None:
# let unexpected exceptions pass through
return False
if self.watcher.records:
msg = 'logs of level {} or higher triggered on {}:\n'.format(
logging.getLevelName(self.level), self.logger.name)
for record in self.watcher.records:
msg += 'logger %s %s:%i: %s\n' % (record.name, record.pathname,
record.lineno,
record.getMessage())
self._raiseFailure(msg)
def slow_test(func):
"""
Decorator that signals that the test takes minutes to run.
Args:
func (callable): test function to be decorated.
Returns:
callable: the decorated function.
"""
@functools.wraps(func)
def _wrapper(*args, **kwargs):
skip_slow = not TEST_OPTIONS['run_slow']
if skip_slow:
raise unittest.SkipTest('Skipping slow tests')
return func(*args, **kwargs)
return _wrapper
def _get_credentials(test_object, test_options):
"""
Finds the credentials for a specific test and options.
Args:
test_object (QiskitTestCase): The test object asking for credentials
test_options (dict): Options after QISKIT_TESTS was parsed by get_test_options.
Returns:
Credentials: set of credentials
Raises:
Exception: When the credential could not be set and they are needed for that set of options
"""
dummy_credentials = Credentials('dummyapiusersloginWithTokenid01',
'https://quantumexperience.ng.bluemix.net/api')
if test_options['mock_online']:
return dummy_credentials
if os.getenv('USE_ALTERNATE_ENV_CREDENTIALS', ''):
# Special case: instead of using the standard credentials mechanism,
# load them from different environment variables. This assumes they
# will always be in place, as is used by the Travis setup.
return Credentials(os.getenv('IBMQ_TOKEN'), os.getenv('IBMQ_URL'))
else:
# Attempt to read the standard credentials.
discovered_credentials = discover_credentials()
if discovered_credentials:
# Decide which credentials to use for testing.
if len(discovered_credentials) > 1:
try:
# Attempt to use QE credentials.
return discovered_credentials[dummy_credentials.unique_id()]
except KeyError:
pass
# Use the first available credentials.
return list(discovered_credentials.values())[0]
# No user credentials were found.
if test_options['rec']:
raise Exception('Could not locate valid credentials. You need them for recording '
'tests against the remote API.')
test_object.log.warning("No user credentials were detected. Running with mocked data.")
test_options['mock_online'] = True
return dummy_credentials
def is_cpp_simulator_available():
"""
Check if executable for C++ simulator is available in the expected
location.
Returns:
bool: True if simulator executable is available
"""
try:
QasmSimulator()
except FileNotFoundError:
return False
return True
def requires_cpp_simulator(test_item):
"""
Decorator that skips test if C++ simulator is not available
Args:
test_item (callable): function or class to be decorated.
Returns:
callable: the decorated function.
"""
reason = 'C++ simulator not found, skipping test'
return unittest.skipIf(not is_cpp_simulator_available(), reason)(test_item)
def requires_qe_access(func):
"""
Decorator that signals that the test uses the online API:
* determines if the test should be skipped by checking environment
variables.
* if the `USE_ALTERNATE_ENV_CREDENTIALS` environment variable is
set, it reads the credentials from an alternative set of environment
variables.
* if the test is not skipped, it reads `qe_token` and `qe_url` from
`Qconfig.py`, environment variables or qiskitrc.
* if the test is not skipped, it appends `qe_token` and `qe_url` as
arguments to the test function.
Args:
func (callable): test function to be decorated.
Returns:
callable: the decorated function.
"""
@functools.wraps(func)
def _wrapper(self, *args, **kwargs):
if TEST_OPTIONS['skip_online']:
raise unittest.SkipTest('Skipping online tests')
credentials = _get_credentials(self, TEST_OPTIONS)
self.using_ibmq_credentials = credentials.is_ibmq()
kwargs.update({'qe_token': credentials.token,
'qe_url': credentials.url})
decorated_func = func
if TEST_OPTIONS['rec'] or TEST_OPTIONS['mock_online']:
# For recording or for replaying existing cassettes, the test should be decorated with
# use_cassette.
decorated_func = VCR.use_cassette()(decorated_func)
return decorated_func(self, *args, **kwargs)
return _wrapper
def _get_http_recorder(test_options):
vcr_mode = 'none'
if test_options['rec']:
vcr_mode = 'new_episodes'
return http_recorder(vcr_mode, Path.CASSETTES.value)
TEST_OPTIONS = get_test_options()
VCR = _get_http_recorder(TEST_OPTIONS)
from qiskit.test.base import QiskitTestCase
from qiskit.test.decorators import (requires_cpp_simulator, requires_qe_access,
slow_test, is_cpp_simulator_available)
from qiskit.test.utils import Path

View File

@ -15,7 +15,7 @@ from qiskit.converters import ast_to_dag, circuit_to_dag
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit import qasm
from ..common import QiskitTestCase
from ..common import QiskitTestCase, Path
class TestAstToDag(QiskitTestCase):
@ -31,7 +31,8 @@ class TestAstToDag(QiskitTestCase):
def test_from_ast_to_dag(self):
"""Test Unroller.execute()"""
ast = qasm.Qasm(filename=self._get_resource_path('qasm/example.qasm')).parse()
ast = qasm.Qasm(filename=self._get_resource_path('example.qasm',
Path.QASMS)).parse()
dag_circuit = ast_to_dag(ast)
expected_result = """\
OPENQASM 2.0;

View File

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Copyright 2018, IBM.
#
# This source code is licensed under the Apache License, Version 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.
"""Custom TestCase for Jobs."""
import time
from qiskit.providers import JobStatus
from ..common import QiskitTestCase
class JobTestCase(QiskitTestCase):
"""Include common functionality when testing jobs."""
def wait_for_initialization(self, job, timeout=1):
"""Waits until the job progress from `INITIALIZING` to a different
status.
"""
waited = 0
wait = 0.1
while job.status() is JobStatus.INITIALIZING:
time.sleep(wait)
waited += wait
if waited > timeout:
self.fail(
msg="The JOB is still initializing after timeout ({}s)"
.format(timeout)
)

View File

@ -17,7 +17,8 @@ from qiskit import (ClassicalRegister, QuantumCircuit, QuantumRegister, compile)
from qiskit import IBMQ, BasicAer
from qiskit.qasm import pi
from ..common import requires_qe_access, JobTestCase, slow_test
from .jobtestcase import JobTestCase
from ..common import requires_qe_access, slow_test
class TestIBMQQobj(JobTestCase):

View File

@ -26,7 +26,8 @@ from qiskit.providers import JobStatus, JobError
from qiskit.providers.ibmq import least_busy
from qiskit.providers.ibmq.exceptions import IBMQBackendError
from qiskit.providers.ibmq.ibmqjob import IBMQJob
from ..common import requires_qe_access, JobTestCase, slow_test
from .jobtestcase import JobTestCase
from ..common import requires_qe_access, slow_test
class TestIBMQJob(JobTestCase):

View File

@ -17,7 +17,7 @@ from qiskit.providers.jobstatus import JobStatus
from qiskit.providers.ibmq.ibmqjob import IBMQJobPreQobj, IBMQJob, API_FINAL_STATES
from qiskit.providers.ibmq.api import ApiError
from qiskit.providers import JobError, JobTimeoutError
from ..common import JobTestCase
from .jobtestcase import JobTestCase
from .._mockutils import new_fake_qobj, FakeBackend

View File

@ -35,8 +35,7 @@ class TestLegacyQasmSimulator(QiskitTestCase):
self.backend = QasmSimulator()
qasm_file_name = 'example.qasm'
qasm_file_path = self._get_resource_path(
'qasm/' + qasm_file_name, Path.TEST)
qasm_file_path = self._get_resource_path(qasm_file_name, Path.QASMS)
self.qc1 = QuantumCircuit.from_qasm_file(qasm_file_path)
qr = QuantumRegister(2, 'q')

View File

@ -14,7 +14,7 @@ from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit
from qiskit import compile
from qiskit.providers.builtinsimulators.qasm_simulator import QasmSimulatorPy
from ..common import QiskitTestCase
from ..common import QiskitTestCase, Path
class TestBuiltinQasmSimulatorPy(QiskitTestCase):
@ -23,7 +23,7 @@ class TestBuiltinQasmSimulatorPy(QiskitTestCase):
def setUp(self):
self.seed = 88
self.backend = QasmSimulatorPy()
qasm_filename = self._get_resource_path('qasm/example.qasm')
qasm_filename = self._get_resource_path('example.qasm', Path.QASMS)
compiled_circuit = QuantumCircuit.from_qasm_file(qasm_filename)
compiled_circuit.name = 'test'
self.qobj = compile(compiled_circuit, backend=self.backend)

View File

@ -14,7 +14,7 @@ import numpy as np
from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit
from qiskit import compile
from qiskit.providers.builtinsimulators.unitary_simulator import UnitarySimulatorPy
from ..common import QiskitTestCase
from ..common import QiskitTestCase, Path
class BuiltinUnitarySimulatorPyTest(QiskitTestCase):
@ -22,7 +22,7 @@ class BuiltinUnitarySimulatorPyTest(QiskitTestCase):
def setUp(self):
self.seed = 88
self.qasm_filename = self._get_resource_path('qasm/example.qasm')
self.qasm_filename = self._get_resource_path('example.qasm', Path.QASMS)
self.backend = UnitarySimulatorPy()
def test_unitary_simulator_py(self):

View File

@ -22,7 +22,7 @@ from qiskit.mapper._compiling import two_qubit_kak
from qiskit.tools.qi.qi import random_unitary_matrix
from qiskit.mapper._mapping import MapperError
from qiskit.converters import circuit_to_dag
from .common import QiskitTestCase
from .common import QiskitTestCase, Path
class FakeQX4BackEnd:
@ -159,7 +159,7 @@ class TestMapper(QiskitTestCase):
def test_random_parameter_circuit(self):
"""Run a circuit with randomly generated parameters."""
circ = QuantumCircuit.from_qasm_file(
self._get_resource_path('qasm/random_n5_d5.qasm'))
self._get_resource_path('random_n5_d5.qasm', Path.QASMS))
coupling_map = [[0, 1], [1, 2], [2, 3], [3, 4]]
shots = 1024
qobj = execute(circ, backend=self.backend,
@ -258,7 +258,7 @@ class TestMapper(QiskitTestCase):
backend = FakeQX5BackEnd()
cmap = backend.configuration().coupling_map
circ = QuantumCircuit.from_qasm_file(
self._get_resource_path('qasm/move_measurements.qasm'))
self._get_resource_path('move_measurements.qasm', Path.QASMS))
dag_circuit = circuit_to_dag(circ)
lay = {('qa', 0): ('q', 0), ('qa', 1): ('q', 1), ('qb', 0): ('q', 15),

View File

@ -15,7 +15,7 @@ import ply
from qiskit.qasm import Qasm, QasmError
from qiskit.qasm._node._node import Node
from .common import QiskitTestCase
from .common import QiskitTestCase, Path
def parse(file_path, prec=15):
@ -31,11 +31,11 @@ def parse(file_path, prec=15):
class TestParser(QiskitTestCase):
"""QasmParser"""
def setUp(self):
self.qasm_file_path = self._get_resource_path('qasm/example.qasm')
self.qasm_file_path = self._get_resource_path('example.qasm', Path.QASMS)
self.qasm_file_path_fail = self._get_resource_path(
'qasm/example_fail.qasm')
'example_fail.qasm', Path.QASMS)
self.qasm_file_path_if = self._get_resource_path(
'qasm/example_if.qasm')
'example_if.qasm', Path.QASMS)
def test_parser(self):
"""should return a correct response for a valid circuit."""

View File

@ -16,11 +16,10 @@ from marshmallow import ValidationError
from qiskit.qobj._schema_validation import (validate_json_against_schema,
_get_validator)
from qiskit import __path__ as qiskit_path
from qiskit.providers.models import (BackendConfiguration, BackendProperties,
BackendStatus, JobStatus)
from qiskit.result import Result
from .common import QiskitTestCase
from .common import QiskitTestCase, Path
logger = logging.getLogger(__name__)
@ -55,8 +54,8 @@ class TestSchemaExamples(QiskitTestCase):
}
def setUp(self):
self.examples_base_path = os.path.join(qiskit_path[0], 'schemas',
'examples')
self.examples_base_path = self._get_resource_path('examples',
Path.SCHEMAS)
def test_examples_are_valid(self):
"""Validate example json files against respective schemas"""

View File

@ -12,7 +12,7 @@ import unittest
from qiskit import qasm
from qiskit.unroll import DagUnroller, JsonBackend
from qiskit.converters import ast_to_dag
from .common import QiskitTestCase
from .common import QiskitTestCase, Path
class UnrollerTest(QiskitTestCase):
@ -24,7 +24,7 @@ class UnrollerTest(QiskitTestCase):
@unittest.skip("Temporary skipping")
def test_dag_to_json(self):
"""Test DagUnroller with JSON backend."""
ast = qasm.Qasm(filename=self._get_resource_path('qasm/example.qasm')).parse()
ast = qasm.Qasm(filename=self._get_resource_path('example.qasm', Path.QASMS)).parse()
dag_circuit = ast_to_dag(ast)
dag_unroller = DagUnroller(dag_circuit, JsonBackend())
json_circuit = dag_unroller.execute()