Repository: qutech/qupulse (language: python)

Issue:
Only trigger import hacking on manual request
Disable import hacking during testing which easily breaks and is only a convenience functionality for developing


Discussion / hints:

Fix patch:
diff --git a/pyproject.toml b/pyproject.toml
index 994c5e81..cd673d74 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -8,3 +8,21 @@ package_dir = "./qupulse"
 filename = "RELEASE_NOTES.rst"
 name = "qupulse"
 issue_format = "`#{issue} <https://github.com/qutech/qupulse/issues/{issue}>`_"
+
+[tool.pytest.ini_options]
+minversion = "6.0"
+python_files = [
+    "*_tests.py",
+    "*_bug.py"
+]
+filterwarnings = [
+    # syntax is action:message_regex:category:module_regex:lineno
+    # we fail on all with a whitelist because a dependency might mess-up passing the correct stacklevel
+    "error::SyntaxWarning",
+    "error::DeprecationWarning",
+    # pytest uses readline which uses collections.abc
+    # "ignore:Using or importing the ABCs from 'collections' instead of from 'collections\.abc\' is deprecated:DeprecationWarning:.*readline.*"
+]
+
+
+
diff --git a/setup.cfg b/setup.cfg
index a77b1e90..fceb89d5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -70,17 +70,6 @@ qupulse =
 qctoolkit =
     *.pyi
 
-[tool:pytest]
-testpaths = tests tests/pulses tests/hardware tests/backward_compatibility
-python_files=*_tests.py *_bug.py
-filterwarnings =
-# syntax is action:message_regex:category:module_regex:lineno
-# we fail on all with a whitelist because a dependency might mess-up passing the correct stacklevel
-    error::SyntaxWarning
-    error::DeprecationWarning
-# pytest uses readline which uses collections.abc
-    ignore:Using or importing the ABCs from \'collections\' instead of from \'collections\.abc\' is deprecated:DeprecationWarning:.*readline.*
-
 [build_sphinx]
 project = 'qupulse'
 version = 0.9


Test patch:
diff --git a/tests/utils/time_type_tests.py b/tests/utils/time_type_tests.py
index 93e11832..cdb9ccff 100644
--- a/tests/utils/time_type_tests.py
+++ b/tests/utils/time_type_tests.py
@@ -5,6 +5,8 @@
 import importlib
 import fractions
 import random
+import os
+
 from unittest import mock
 
 try:
@@ -18,6 +20,9 @@
 import qupulse.utils.types as qutypes
 
 
+MOCK_GMPY2_AS_MISSING = bool(os.getenv("QUPULSE_TESTS_MOCK_GMPY2_AS_MISSING"))
+
+
 @contextlib.contextmanager
 def mock_missing_module(module_name: str):
     exit_stack = contextlib.ExitStack()
@@ -43,21 +48,191 @@ def mock_import(name, *args, **kwargs):
         yield
 
 
-class TestTimeType(unittest.TestCase):
-    """The fallback test is here for convenience while developing. The fallback is also tested by the CI explicitly"""
+@unittest.skipIf(gmpy2 and not MOCK_GMPY2_AS_MISSING, "Not explicitly included. "
+                                                      "Define QUPULSE_TESTS_MOCK_GMPY2_AS_MISSING to include.")
+class TestTimeTypeDevFallback(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        with mock_missing_module('gmpy2'):
+            cls.fallback_qutypes = importlib.reload(qutypes)
+
+    def test_fraction_fallback(self):
+        self.assertIs(fractions.Fraction, self.fallback_qutypes.TimeType._InternalType)
 
-    _fallback_qutypes = None
+    def test_fraction_time_from_fraction_fallback(self):
+        assert_from_fraction_works(self, self.fallback_qutypes.TimeType)
 
-    @property
-    def fallback_qutypes(self):
-        if not self._fallback_qutypes:
-            if gmpy2:
-                with mock_missing_module('gmpy2'):
-                    self._fallback_qutypes = importlib.reload(qutypes)
+    def test_fraction_time_from_float_exact_fallback(self):
+        assert_from_float_exact_works(self, self.fallback_qutypes.TimeType)
 
-            else:
-                self._fallback_qutypes = qutypes
-        return self._fallback_qutypes
+    def test_fraction_time_from_float_with_precision_fallback(self):
+        assert_fraction_time_from_float_with_precision_works(self, self.fallback_qutypes.TimeType)
+
+    def test_from_float_no_extra_args_fallback(self):
+        assert_from_float_no_extra_args_works(self, self.fallback_qutypes.TimeType)
+
+    def test_try_from_any_fallback(self):
+        assert_try_from_any_works(self, self.fallback_qutypes.TimeType)
+
+    def test_comparisons_work_fallback(self):
+        assert_comparisons_work(self, self.fallback_qutypes.TimeType)
+
+
+def assert_from_fraction_works(test: unittest.TestCase, time_type):
+    t = time_type.from_fraction(43, 12)
+    test.assertIsInstance(t, time_type)
+    test.assertEqual(t, fractions.Fraction(43, 12))
+
+
+def assert_from_float_exact_works(test: unittest.TestCase, time_type):
+    test.assertEqual(time_type.from_float(123 / 931, 0),
+                     fractions.Fraction(123 / 931))
+
+
+def assert_fraction_time_from_float_with_precision_works(test: unittest.TestCase, time_type):
+    test.assertEqual(time_type.from_float(1000000 / 1000001, 1e-5),
+                     fractions.Fraction(1))
+    test.assertEqual(time_type.from_float(2.50000000000008, absolute_error=1e-10),
+                     time_type.from_fraction(5, 2))
+    test.assertEqual(time_type.from_float(9926.666666667, absolute_error=1e-9),
+                     time_type.from_fraction(29780, 3))
+
+
+def assert_from_float_no_extra_args_works(test: unittest.TestCase, time_type):
+    # test that float(from_float(x)) == x
+    base_floats = [4/5, 1, 1000, 0, np.pi, 1.23456789**99, 1e-100, 2**53]
+    n_steps = 10**2
+
+    def float_generator():
+        for f in base_floats:
+            for _ in range(n_steps):
+                yield f
+                f = np.nextafter(f, float('inf'))
+
+        for f in base_floats:
+            for _ in range(n_steps):
+                yield f
+                f = np.nextafter(f, float('-inf'))
+
+    for x in float_generator():
+        t = time_type.from_float(x)
+        t2x = float(t)
+        test.assertEqual(x, t2x)
+        test.assertGreater(t, np.nextafter(x, float('-inf')))
+        test.assertLess(t, np.nextafter(x, float('inf')))
+
+
+def assert_try_from_any_works(test: unittest.TestCase, time_type):
+    try_from_any = time_type._try_from_any
+
+    # these duck types are here because isinstance(<gmpy2 obj>, numbers.<NumberType>) is version dependent
+    class DuckTypeWrapper:
+        def __init__(self, value):
+            self.value = value
+
+        def __repr__(self):
+            return f'{type(self)}({self.value})'
+
+    class DuckInt(DuckTypeWrapper):
+        def __int__(self):
+            return int(self.value)
+
+    class DuckFloat(DuckTypeWrapper):
+        def __float__(self):
+            return float(self.value)
+
+    class DuckIntFloat(DuckFloat):
+        def __int__(self):
+            return int(self.value)
+
+    class DuckRational:
+        def __init__(self, numerator, denominator):
+            self.numerator = numerator
+            self.denominator = denominator
+
+        def __repr__(self):
+            return f'{type(self)}({self.numerator}, {self.denominator})'
+
+    for_array_tests = []
+
+    signed_int_types = [int, sympy.Integer, np.int8, np.int16, np.int32, np.int64, DuckInt, DuckIntFloat]
+    if gmpy2:
+        signed_int_types.append(gmpy2.mpz)
+
+    for s_t in signed_int_types:
+        for val in (1, 17, -17):
+            any_val = s_t(val)
+            expected_val = time_type.from_fraction(int(val), 1)
+            test.assertEqual(expected_val, try_from_any(any_val))
+            for_array_tests.append((expected_val, any_val))
+
+    unsigned_int_types = [np.uint8, np.uint16, np.uint32, np.uint]
+    for u_t in unsigned_int_types:
+        for val in (1, 17):
+            any_val = u_t(val)
+            expected_val = time_type.from_fraction(int(val), 1)
+            test.assertEqual(expected_val, try_from_any(any_val))
+            for_array_tests.append((expected_val, any_val))
+
+    rational_types = [fractions.Fraction, sympy.Rational, time_type.from_fraction, DuckRational]
+    if gmpy2:
+        rational_types.append(gmpy2.mpq)
+    for r_t in rational_types:
+        for num, den in ((1, 3), (-3, 8), (17, 5)):
+            any_val = r_t(num, den)
+            expected_val = time_type.from_fraction(num, den)
+            test.assertEqual(expected_val, try_from_any(any_val))
+            for_array_tests.append((expected_val, any_val))
+
+    float_types = [float, sympy.Float, DuckFloat, DuckIntFloat]
+    if gmpy2:
+        float_types.append(gmpy2.mpfr)
+    for f_t in float_types:
+        for val in (3.4, -3., 1.):
+            any_val = f_t(val)
+            expected_val = time_type.from_float(val)
+            test.assertEqual(expected_val, try_from_any(any_val))
+            for_array_tests.append((expected_val, any_val))
+
+    arr = np.array(for_array_tests, dtype='O')
+    any_arr = arr[:, 1]
+    expected_arr = arr[:, 0]
+    np.testing.assert_equal(expected_arr, try_from_any(any_arr))
+
+
+def assert_comparisons_work(test: unittest.TestCase, time_type):
+    tt = time_type.from_float(1.1)
+
+    test.assertLess(tt, 4)
+    test.assertLess(tt, 4.)
+    test.assertLess(tt, time_type.from_float(4.))
+    test.assertLess(tt, float('inf'))
+
+    test.assertLessEqual(tt, 4)
+    test.assertLessEqual(tt, 4.)
+    test.assertLessEqual(tt, time_type.from_float(4.))
+    test.assertLessEqual(tt, float('inf'))
+
+    test.assertGreater(tt, 1)
+    test.assertGreater(tt, 1.)
+    test.assertGreater(tt, time_type.from_float(1.))
+    test.assertGreater(tt, float('-inf'))
+
+    test.assertGreaterEqual(tt, 1)
+    test.assertGreaterEqual(tt, 1.)
+    test.assertGreaterEqual(tt, time_type.from_float(1.))
+    test.assertGreaterEqual(tt, float('-inf'))
+
+    test.assertFalse(tt == float('nan'))
+    test.assertFalse(tt <= float('nan'))
+    test.assertFalse(tt >= float('nan'))
+    test.assertFalse(tt < float('nan'))
+    test.assertFalse(tt > float('nan'))
+
+
+class TestTimeType(unittest.TestCase):
+    """The fallback test is here for convenience while developing and only triggered if the environment variable is set.
+    The fallback is also tested by the CI explicitly"""
 
     def test_non_finite_float(self):
         with self.assertRaisesRegex(ValueError, 'Cannot represent'):
@@ -67,76 +242,17 @@ def test_non_finite_float(self):
         with self.assertRaisesRegex(ValueError, 'Cannot represent'):
             qutypes.TimeType.from_float(float('nan'))
 
-    def test_fraction_fallback(self):
-        self.assertIs(fractions.Fraction, self.fallback_qutypes.TimeType._InternalType)
-
-    def assert_from_fraction_works(self, time_type):
-        t = time_type.from_fraction(43, 12)
-        self.assertIsInstance(t, time_type)
-        self.assertEqual(t, fractions.Fraction(43, 12))
-
     def test_fraction_time_from_fraction(self):
-        self.assert_from_fraction_works(qutypes.TimeType)
-
-    @unittest.skipIf(gmpy2 is None, "fallback already tested")
-    def test_fraction_time_from_fraction_fallback(self):
-        self.assert_from_fraction_works(self.fallback_qutypes.TimeType)
-
-    def assert_from_float_exact_works(self, time_type):
-        self.assertEqual(time_type.from_float(123 / 931, 0),
-                         fractions.Fraction(123 / 931))
+        assert_from_fraction_works(self, qutypes.TimeType)
 
     def test_fraction_time_from_float_exact(self):
-        self.assert_from_float_exact_works(qutypes.TimeType)
-
-    @unittest.skipIf(gmpy2 is None, "fallback already tested")
-    def test_fraction_time_from_float_exact_fallback(self):
-        self.assert_from_float_exact_works(self.fallback_qutypes.TimeType)
-
-    def assert_fraction_time_from_float_with_precision_works(self, time_type):
-        self.assertEqual(time_type.from_float(1000000 / 1000001, 1e-5),
-                         fractions.Fraction(1))
-        self.assertEqual(time_type.from_float(2.50000000000008, absolute_error=1e-10),
-                         time_type.from_fraction(5, 2))
-        self.assertEqual(time_type.from_float(9926.666666667, absolute_error=1e-9),
-                         time_type.from_fraction(29780, 3))
+        assert_from_float_exact_works(self, qutypes.TimeType)
 
     def test_fraction_time_from_float_with_precision(self):
-        self.assert_fraction_time_from_float_with_precision_works(qutypes.TimeType)
-
-    @unittest.skipIf(gmpy2 is None, "fallback already tested")
-    def test_fraction_time_from_float_with_precision_fallback(self):
-        self.assert_fraction_time_from_float_with_precision_works(self.fallback_qutypes.TimeType)
-
-    def assert_from_float_no_extra_args_works(self, time_type):
-        # test that float(from_float(x)) == x
-        base_floats = [4/5, 1, 1000, 0, np.pi, 1.23456789**99, 1e-100, 2**53]
-        n_steps = 10**2
-
-        def float_generator():
-            for f in base_floats:
-                for _ in range(n_steps):
-                    yield f
-                    f = np.nextafter(f, float('inf'))
-
-            for f in base_floats:
-                for _ in range(n_steps):
-                    yield f
-                    f = np.nextafter(f, float('-inf'))
-
-        for x in float_generator():
-            t = time_type.from_float(x)
-            t2x = float(t)
-            self.assertEqual(x, t2x)
-            self.assertGreater(t, np.nextafter(x, float('-inf')))
-            self.assertLess(t, np.nextafter(x, float('inf')))
+        assert_fraction_time_from_float_with_precision_works(self, qutypes.TimeType)
 
     def test_from_float_no_extra_args(self):
-        self.assert_from_float_exact_works(qutypes.TimeType)
-
-    @unittest.skipIf(gmpy2 is None, "fallback already tested")
-    def test_from_float_no_extra_args_fallback(self):
-        self.assert_from_float_exact_works(self.fallback_qutypes.TimeType)
+        assert_from_float_exact_works(self, qutypes.TimeType)
 
     def test_from_float_exceptions(self):
         with self.assertRaisesRegex(ValueError, '> 0'):
@@ -145,122 +261,11 @@ def test_from_float_exceptions(self):
         with self.assertRaisesRegex(ValueError, '<= 1'):
             qutypes.time_from_float(.8, 2)
 
-    def assert_try_from_any_works(self, time_type):
-        try_from_any = time_type._try_from_any
-
-        # these duck types are here because isinstance(<gmpy2 obj>, numbers.<NumberType>) is version dependent
-        class DuckTypeWrapper:
-            def __init__(self, value):
-                self.value = value
-
-            def __repr__(self):
-                return f'{type(self)}({self.value})'
-
-        class DuckInt(DuckTypeWrapper):
-            def __int__(self):
-                return int(self.value)
-
-        class DuckFloat(DuckTypeWrapper):
-            def __float__(self):
-                return float(self.value)
-
-        class DuckIntFloat(DuckFloat):
-            def __int__(self):
-                return int(self.value)
-
-        class DuckRational:
-            def __init__(self, numerator, denominator):
-                self.numerator = numerator
-                self.denominator = denominator
-
-            def __repr__(self):
-                return f'{type(self)}({self.numerator}, {self.denominator})'
-
-        for_array_tests = []
-
-        signed_int_types = [int, sympy.Integer, np.int8, np.int16, np.int32, np.int64, DuckInt, DuckIntFloat]
-        if gmpy2:
-            signed_int_types.append(gmpy2.mpz)
-
-        for s_t in signed_int_types:
-            for val in (1, 17, -17):
-                any_val = s_t(val)
-                expected_val = time_type.from_fraction(int(val), 1)
-                self.assertEqual(expected_val, try_from_any(any_val))
-                for_array_tests.append((expected_val, any_val))
-
-        unsigned_int_types = [np.uint8, np.uint16, np.uint32, np.uint]
-        for u_t in unsigned_int_types:
-            for val in (1, 17):
-                any_val = u_t(val)
-                expected_val = time_type.from_fraction(int(val), 1)
-                self.assertEqual(expected_val, try_from_any(any_val))
-                for_array_tests.append((expected_val, any_val))
-
-        rational_types = [fractions.Fraction, sympy.Rational, time_type.from_fraction, DuckRational]
-        if gmpy2:
-            rational_types.append(gmpy2.mpq)
-        for r_t in rational_types:
-            for num, den in ((1, 3), (-3, 8), (17, 5)):
-                any_val = r_t(num, den)
-                expected_val = time_type.from_fraction(num, den)
-                self.assertEqual(expected_val, try_from_any(any_val))
-                for_array_tests.append((expected_val, any_val))
-
-        float_types = [float, sympy.Float, DuckFloat, DuckIntFloat]
-        if gmpy2:
-            float_types.append(gmpy2.mpfr)
-        for f_t in float_types:
-            for val in (3.4, -3., 1.):
-                any_val = f_t(val)
-                expected_val = time_type.from_float(val)
-                self.assertEqual(expected_val, try_from_any(any_val))
-                for_array_tests.append((expected_val, any_val))
-
-        arr = np.array(for_array_tests, dtype='O')
-        any_arr = arr[:, 1]
-        expected_arr = arr[:, 0]
-        np.testing.assert_equal(expected_arr, try_from_any(any_arr))
-
     def test_try_from_any(self):
-        self.assert_try_from_any_works(qutypes.TimeType)
-        self.assert_try_from_any_works(self.fallback_qutypes.TimeType)
-
-    def assert_comparisons_work(self, time_type):
-        tt = time_type.from_float(1.1)
-
-        self.assertLess(tt, 4)
-        self.assertLess(tt, 4.)
-        self.assertLess(tt, time_type.from_float(4.))
-        self.assertLess(tt, float('inf'))
-
-        self.assertLessEqual(tt, 4)
-        self.assertLessEqual(tt, 4.)
-        self.assertLessEqual(tt, time_type.from_float(4.))
-        self.assertLessEqual(tt, float('inf'))
-
-        self.assertGreater(tt, 1)
-        self.assertGreater(tt, 1.)
-        self.assertGreater(tt, time_type.from_float(1.))
-        self.assertGreater(tt, float('-inf'))
-
-        self.assertGreaterEqual(tt, 1)
-        self.assertGreaterEqual(tt, 1.)
-        self.assertGreaterEqual(tt, time_type.from_float(1.))
-        self.assertGreaterEqual(tt, float('-inf'))
-
-        self.assertFalse(tt == float('nan'))
-        self.assertFalse(tt <= float('nan'))
-        self.assertFalse(tt >= float('nan'))
-        self.assertFalse(tt < float('nan'))
-        self.assertFalse(tt > float('nan'))
+        assert_try_from_any_works(self, qutypes.TimeType)
 
     def test_comparisons_work(self):
-        self.assert_comparisons_work(qutypes.TimeType)
-
-    @unittest.skipIf(gmpy2 is None, "fallback already tested")
-    def test_comparisons_work_fallback(self):
-        self.assert_comparisons_work(self.fallback_qutypes.TimeType)
+        assert_comparisons_work(self, qutypes.TimeType)
 
 
 def get_some_floats(seed=42, n=1000):