diff --git a/qiskit_experiments/warnings.py b/qiskit_experiments/warnings.py index c5197ef07d..19c9733025 100644 --- a/qiskit_experiments/warnings.py +++ b/qiskit_experiments/warnings.py @@ -29,3 +29,7 @@ HAS_DYNAMICS = LazyImportTester( "qiskit_dynamics", name="qiskit-dynamics", install="pip install qiskit-dynamics" ) + +HAS_TESTTOOLS = LazyImportTester( + "testtools", name="testtools", install="pip install testtools" +) diff --git a/test/base.py b/test/base.py index d1ad6e8ed6..c6658a1100 100644 --- a/test/base.py +++ b/test/base.py @@ -16,14 +16,16 @@ import os import json import pickle +import sys +import unittest import warnings from typing import Any, Callable, Optional import fixtures import uncertainties -from qiskit.test import QiskitTestCase from qiskit.utils.deprecation import deprecate_func +import qiskit_experiments.warnings from qiskit_experiments.framework import ( ExperimentDecoder, ExperimentEncoder, @@ -36,18 +38,62 @@ TEST_TIMEOUT = os.environ.get("TEST_TIMEOUT", 60) -class QiskitExperimentsTestCase(QiskitTestCase): +# If testtools is installed use that as a (mostly) drop in replacement for +# unittest's TestCase. This will enable the fixtures used for capturing stdout +# stderr, and pylogging to attach the output to stestr's result stream. +if qiskit_experiments.warnings.HAS_TESTTOOLS: + import testtools + + class BaseTestCase(testtools.TestCase): + """Base test class.""" + + # testtools maintains their own version of assert functions which mostly + # behave as value adds to the std unittest assertion methods. However, + # for assertEquals and assertRaises modern unittest has diverged from + # the forks in testtools and offer more (or different) options that are + # incompatible testtools versions. Just use the stdlib versions so that + # our tests work as expected. + assertRaises = unittest.TestCase.assertRaises + assertEqual = unittest.TestCase.assertEqual + +else: + + class BaseTestCase(unittest.TestCase): + """Base test class.""" + + pass + + + + +# TODO: copy enforce_subclasses_call decorator? +class QiskitExperimentsTestCase(BaseTestCase): """Qiskit Experiments specific extra functionality for test cases.""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.__setup_called = False + self.__teardown_called = False + def setUp(self): super().setUp() self.useFixture(fixtures.Timeout(TEST_TIMEOUT, gentle=True)) + if self.__setup_called: + raise ValueError( + "In File: %s\n" + "TestCase.setUp was already called. Do not explicitly call " + "setUp from your tests. In your own setUp, use super to call " + "the base setUp." % (sys.modules[self.__class__.__module__].__file__,) + ) + self.__setup_called = True @classmethod def setUpClass(cls): """Set-up test class.""" super().setUpClass() + warnings.filterwarnings("error", category=DeprecationWarning) + # Some functionality may be deprecated in Qiskit Experiments. If the deprecation warnings aren't # filtered, the tests will fail as ``QiskitTestCase`` sets all warnings to be treated as an error # by default. @@ -60,6 +106,17 @@ def setUpClass(cls): for msg in allow_deprecationwarning_message: warnings.filterwarnings("default", category=DeprecationWarning, message=msg) + def tearDown(self): + super().tearDown() + if self.__teardown_called: + raise ValueError( + "In File: %s\n" + "TestCase.tearDown was already called. Do not explicitly call " + "tearDown from your tests. In your own tearDown, use super to " + "call the base tearDown." % (sys.modules[self.__class__.__module__].__file__,) + ) + self.__teardown_called = True + def assertExperimentDone( self, experiment_data: ExperimentData, diff --git a/test/framework/test_store_init_args.py b/test/framework/test_store_init_args.py index df46c68a8d..aa30bc56c1 100644 --- a/test/framework/test_store_init_args.py +++ b/test/framework/test_store_init_args.py @@ -12,8 +12,8 @@ """Tests for base experiment framework.""" -from qiskit.test import QiskitTestCase from qiskit_experiments.framework.store_init_args import StoreInitArgs +from test.base import QiskitExperimentsTestCase class StoreArgsBase(StoreInitArgs): @@ -58,7 +58,7 @@ def __init__(self, a, b, c="default_c", d="default_d"): pass -class TestSettings(QiskitTestCase): +class TestSettings(QiskitExperimentsTestCase): """Test Settings mixin""" # pylint: disable = missing-function-docstring diff --git a/test/library/characterization/test_cross_resonance_hamiltonian.py b/test/library/characterization/test_cross_resonance_hamiltonian.py index 1df5c80aae..f06b1e407c 100644 --- a/test/library/characterization/test_cross_resonance_hamiltonian.py +++ b/test/library/characterization/test_cross_resonance_hamiltonian.py @@ -21,7 +21,7 @@ from ddt import ddt, data, unpack from qiskit import QuantumCircuit, pulse, qpy, quantum_info as qi from qiskit.providers.fake_provider import FakeBogotaV2 -from qiskit.extensions.hamiltonian_gate import HamiltonianGate +from qiskit.circuit.library.hamiltonian_gate import HamiltonianGate from qiskit_aer import AerSimulator from qiskit_experiments.library.characterization import cr_hamiltonian