Repository: myokit/myokit (language: python)

Issue:
Unit tests with opencl hanging?
```
# Install OpenCL (CPU) runtime
# https://software.intel.com/en-us/articles/opencl-drivers#latest_CPU_runtime
PACKAGE_URL=http://registrationcenter-download.intel.com/akdlm/irc_nas/12556/opencl_runtime_16.1.2_x64_rh_6.4.0.37.tgz
PACKAGE_NAME=opencl_runtime_16.1.2_x64_rh_6.4.0.37
wget -q ${PACKAGE_URL} -O /tmp/opencl_runtime.tgz
tar -xzf /tmp/opencl_runtime.tgz -C /tmp
sed 's/decline/accept/g' -i /tmp/${PACKAGE_NAME}/silent.cfg
/tmp/${PACKAGE_NAME}/install.sh -s /tmp/${PACKAGE_NAME}/silent.cfg
```


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/install-ubuntu-dependencies.sh b/.github/workflows/install-ubuntu-dependencies.sh
index 0861bf47f..e89b51a84 100755
--- a/.github/workflows/install-ubuntu-dependencies.sh
+++ b/.github/workflows/install-ubuntu-dependencies.sh
@@ -12,8 +12,8 @@ apt-get install -y opencl-headers ocl-icd-opencl-dev;
 
 # Install OpenCL (CPU) runtime
 # https://software.intel.com/en-us/articles/opencl-drivers#latest_CPU_runtime
-PACKAGE_URL=http://registrationcenter-download.intel.com/akdlm/irc_nas/12556/opencl_runtime_16.1.2_x64_rh_6.4.0.37.tgz
-PACKAGE_NAME=opencl_runtime_16.1.2_x64_rh_6.4.0.37
+PACKAGE_URL=https://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/15532/l_opencl_p_18.1.0.015.tgz
+PACKAGE_NAME=l_opencl_p_18.1.0.015
 wget -q ${PACKAGE_URL} -O /tmp/opencl_runtime.tgz
 tar -xzf /tmp/opencl_runtime.tgz -C /tmp
 sed 's/decline/accept/g' -i /tmp/${PACKAGE_NAME}/silent.cfg


Test patch:
diff --git a/myokit/tests/test_aux.py b/myokit/tests/test_aux.py
index 0676dc3e8..c7ef5ae8a 100755
--- a/myokit/tests/test_aux.py
+++ b/myokit/tests/test_aux.py
@@ -297,6 +297,45 @@ def test_step(self):
         # Test the step() method.
         m1 = myokit.load_model(os.path.join(DIR_DATA, 'beeler-1977-model.mmt'))
 
+        # For whatever reason, CI gives slightly different final digits some
+        # times.
+        r1 = re.compile(r'[a-zA-Z]\w*\.[a-zA-Z]\w*\s+([^\s]+)\s+([^\s]+)')
+        r2 = re.compile(r'\s+([0-9e.+-]+)')
+        r3 = re.compile(r'^[ ^]*$')
+        r4 = re.compile(r'^Found \([0-9]+\) small mismatches.$')
+
+        def almost_equal(x, y):
+            # Easy checks
+            if x == y:
+                return True
+
+            # Get numbers with regex and compare
+            # First case: assume two numbers on each line. 2nd is tested
+            g1, g2 = r1.match(x), r1.match(y)
+            if g1 is not None and g2 is not None:
+                x, y = float(g1.group(2)), float(g2.group(2))
+                return myokit.float.close(x, y)
+
+            # Second case: one number per line.
+            g1, g2 = r2.match(x), r2.match(y)
+            if g1 is not None and g2 is not None:
+                x, y = float(g1.group(1)), float(g2.group(1))
+                return myokit.float.close(x, y)
+
+            # Third case: "   ^^^ "
+            # These don't matter: we've already seen that the floats are close
+            g1, g2 = r3.match(x), r3.match(y)
+            if g1 is not None and g2 is not None:
+                return True
+
+            # Fourth case: Found (x) small differences
+            # These don't matter: we've already seen that the floats are close
+            g1, g2 = r4.match(x), r4.match(y)
+            if g1 is not None and g2 is not None:
+                return True
+
+            return x == y
+
         # Test simple output
         x = myokit.step(m1).splitlines()
         y = [
@@ -315,7 +354,7 @@ def test_step(self):
             '-' * 79,
         ]
         for a, b in zip(x, y):
-            self.assertEqual(a, b)
+            self.assertTrue(almost_equal(a, b))
         self.assertEqual(len(x), len(y))
 
         # Test with an initial state
@@ -338,16 +377,8 @@ def test_step(self):
             'ix1.x1        1.00000000000000006e-01  -4.72598388279933061e-03',
             '-' * 79,
         ]
-        r1 = re.compile(r'[a-zA-Z]\w*\.[a-zA-Z]\w*\s+([^\s]+)\s+([^\s]+)')
         for a, b in zip(x, y):
-            g1 = r1.match(a)
-            g2 = r1.match(b)
-            if g1 is not None and g2 is not None:
-                self.assertEqual(g1.group(1), g2.group(1))
-                a, b = float(g1.group(2)), float(g2.group(2))
-                self.assertTrue(myokit.float.close(a, b))
-            else:
-                self.assertEqual(a, b)
+            self.assertTrue(almost_equal(a, b))
         self.assertEqual(len(x), len(y))
 
         # Test comparison against another model (with both models the same)
@@ -386,7 +417,7 @@ def test_step(self):
             '-' * 79,
         ]
         for a, b in zip(x, y):
-            self.assertEqual(a, b)
+            self.assertTrue(almost_equal(a, b))
         self.assertEqual(len(x), len(y))
 
         # Test comparison against another model, with an initial state
@@ -423,35 +454,19 @@ def test_step(self):
             'Model check completed without errors.',
             '-' * 79,
         ]
-        r2 = re.compile(r'\s+([^\s]+)')
         for a, b in zip(x, y):
-            g1 = r1.match(a)
-            g2 = r1.match(b)
-            if g1 is not None and g2 is not None:
-                self.assertEqual(g1.group(1), g2.group(1))
-                a, b = float(g1.group(2)), float(g2.group(2))
-                self.assertTrue(myokit.float.close(a, b))
-            else:
-                g1 = r2.match(a)
-                g2 = r2.match(b)
-                if g1 is not None and g2 is not None:
-                    a, b = float(g1.group(1)), float(g2.group(1))
-                    self.assertTrue(myokit.float.close(a, b))
-                else:
-                    self.assertEqual(a, b)
+            self.assertTrue(almost_equal(a, b))
         self.assertEqual(len(x), len(y))
 
         # Test comparison against stored data
-        ref = [
-            -3.97224086575331868e-04,       # Numerically indistinguishable
-            -1.56608433137725457e-09,
-            7.48738392280519777e-02,        # Tiny error
-            -1.78891889478854579e-03,
-            3.06255006833574140e-04,        # Sign error
-            -5.11993904291850035e-06,
-            3.76748229376431740e-04,        # Large error
-            -3.21682814207918156e-05,       # Exponent
-        ]
+        # And get all the different error outputs
+        ref = m1.evaluate_derivatives()
+        ref[0] += 5e-20  # Numerically indistinguishable
+        ref[2] += 1e-16  # Tiny error
+        ref[4] *= -1     # Sign error
+        ref[6] += 2e-4   # Big error
+        ref[7] *= 100    # Exponent
+
         x = myokit.step(m1, reference=ref).splitlines()
         y = [
             'Evaluating state vector derivatives...',
@@ -459,16 +474,16 @@ def test_step(self):
             'Name         Initial value             Derivative at t=0       ',
             '-' * 79,
             'membrane.V   -8.46219999999999999e+01  -3.97224086575331814e-04',
-            '                                       -3.97224086575331868e-04'
+            '                                       -3.97224086575331760e-04'
             ' <= 1 eps',
             '',
             'calcium.Cai   1.99999999999999991e-07  -1.56608433137725457e-09',
             '                                       -1.56608433137725457e-09',
             '',
             'ina.m         1.00000000000000002e-02   7.48738392280519083e-02',
-            '                                        7.48738392280519777e-02'
-            ' ~ 4.2 eps',
-            '                                                        ^^^^^^^',
+            '                                        7.48738392280520054e-02'
+            ' ~ 5.8 eps',
+            '                                                      ^^^^^^^^^',
             'ina.h         9.89999999999999991e-01  -1.78891889478854579e-03',
             '                                       -1.78891889478854579e-03',
             '',
@@ -480,7 +495,7 @@ def test_step(self):
             '                                       -5.11993904291850035e-06',
             '',
             'isi.f         9.89999999999999991e-01   1.88374114688215870e-04',
-            '                                        3.76748229376431740e-04'
+            '                                        3.88374114688215880e-04'
             ' X',
             '                                        ^^^^^^^^^^^^^^^^^^^^^^^',
             'ix1.x1        4.00000000000000019e-04  -3.21682814207918156e-07',
@@ -492,7 +507,7 @@ def test_step(self):
             '-' * 79,
         ]
         for a, b in zip(x, y):
-            self.assertEqual(a, b)
+            self.assertTrue(almost_equal(a, b))
         self.assertEqual(len(x), len(y))
 
         # Test positive/negative zero comparison
diff --git a/myokit/tests/test_model.py b/myokit/tests/test_model.py
index 581cbadc0..d50c35329 100755
--- a/myokit/tests/test_model.py
+++ b/myokit/tests/test_model.py
@@ -12,6 +12,7 @@
 from __future__ import print_function, unicode_literals
 
 import pickle
+import re
 import unittest
 
 import myokit
@@ -510,33 +511,53 @@ def test_format_state_derivatives(self):
         self.maxDiff = None
         m = myokit.load_model('example')
 
+        # For whatever reason, CI gives slightly different final digits some
+        # times.
+        r1 = re.compile(
+            r'^[a-zA-Z]\w*\.[a-zA-Z]\w*\s+=\s+([^\s]+)\s+dot\s+=\s+([^\s]+)$')
+
+        def almost_equal(x, y):
+            if x == y:
+                return True
+            g1, g2 = r1.match(x), r1.match(y)
+            if g1 is None or g2 is None:
+                return False
+            x, y = float(g1.group(2)), float(g2.group(2))
+            return myokit.float.close(x, y)
+
         # Test without arguments
-        self.assertEqual(
-            m.format_state_derivatives(),
-'membrane.V = -84.5286                   dot = -5.68008003798848027e-02\n'  # noqa
-'ina.m      = 0.0017                     dot = -4.94961486033834719e-03\n'  # noqa
-'ina.h      = 0.9832                     dot =  9.02025299127830887e-06\n'  # noqa
-'ina.j      = 0.995484                   dot = -3.70409866928434243e-04\n'  # noqa
-'ica.d      = 3e-06                      dot =  3.68067721821794798e-04\n'  # noqa
-'ica.f      = 1.0                        dot = -3.55010150519739432e-07\n'  # noqa
-'ik.x       = 0.0057                     dot = -2.04613933160084307e-07\n'  # noqa
-'ica.Ca_i   = 0.0002                     dot = -6.99430692442154227e-06'    # noqa
-        )
+        x = m.format_state_derivatives().splitlines()
+        y = [
+'membrane.V = -84.5286                   dot = -5.68008003798848027e-02', # noqa
+'ina.m      = 0.0017                     dot = -4.94961486033834719e-03', # noqa
+'ina.h      = 0.9832                     dot =  9.02025299127830887e-06', # noqa
+'ina.j      = 0.995484                   dot = -3.70409866928434243e-04', # noqa
+'ica.d      = 3e-06                      dot =  3.68067721821794798e-04', # noqa
+'ica.f      = 1.0                        dot = -3.55010150519739432e-07', # noqa
+'ik.x       = 0.0057                     dot = -2.04613933160084307e-07', # noqa
+'ica.Ca_i   = 0.0002                     dot = -6.99430692442154227e-06'  # noqa
+        ]
+        for a, b in zip(x, y):
+            self.assertTrue(almost_equal(a, b))
+        self.assertEqual(len(x), len(y))
 
         # Test with state argument
         state1 = [1, 2, 3, 4, 5, 6, 7, 8]
         state1[2] = 536.46745856785678567845745637
-        self.assertEqual(
-            m.format_state_derivatives(state1),
-'membrane.V = 1                          dot =  1.90853168050245158e+07\n'  # noqa
-'ina.m      = 2                          dot = -1.56738349674489310e+01\n'  # noqa
-'ina.h      =  5.36467458567856738e+02   dot = -3.05729251015767022e+03\n'  # noqa
-'ina.j      = 4                          dot = -1.15731427949362953e+00\n'  # noqa
-'ica.d      = 5                          dot = -1.85001944916516836e-01\n'  # noqa
-'ica.f      = 6                          dot = -2.15435819790876573e-02\n'  # noqa
-'ik.x       = 7                          dot = -1.25154369264425316e-02\n'  # noqa
-'ica.Ca_i   = 8                          dot = -5.63431267451130036e-01'  # noqa                                       ^ ^^    ^ ---------   ^
-        )
+        x = m.format_state_derivatives(state1).splitlines()
+        y = [
+'membrane.V = 1                          dot =  1.90853168050245158e+07', # noqa
+'ina.m      = 2                          dot = -1.56738349674489310e+01', # noqa
+'ina.h      =  5.36467458567856738e+02   dot = -3.05729251015767022e+03', # noqa
+'ina.j      = 4                          dot = -1.15731427949362953e+00', # noqa
+'ica.d      = 5                          dot = -1.85001944916516836e-01', # noqa
+'ica.f      = 6                          dot = -2.15435819790876573e-02', # noqa
+'ik.x       = 7                          dot = -1.25154369264425316e-02', # noqa
+'ica.Ca_i   = 8                          dot = -5.63431267451130036e-01', # noqa                                       ^ ^^    ^ ---------   ^
+        ]
+        for a, b in zip(x, y):
+            self.assertTrue(almost_equal(a, b))
+        self.assertEqual(len(x), len(y))
 
         # Test with invalid state argument
         self.assertRaisesRegex(
@@ -2077,3 +2098,4 @@ def test_warnings(self):
 
 if __name__ == '__main__':
     unittest.main()
+