Repository: lbl-anp/becquerel (language: python)

Issue:
Flaky NNDC tests failing
We still have the flaky tests failing in the CI. See this [pipeline](https://github.com/lbl-anp/becquerel/runs/5779050487?check_suite_focus=true)

- [x] Add to dev requirements


Discussion / hints:
Those tests are indeed marked with `flaky` but it's possible they failed all three times if the NNDC server was down for a few seconds:
```py
@pytest.mark.flaky(reruns=3)
class TestDecayRadiationQuery(NNDCQueryTests):
```
@jvavrek can we all a timeout between reruns?
Oh and the package is not `pytest-flaky` but [`pytest-rerunfailures`](https://github.com/pytest-dev/pytest-rerunfailures), which is in `requirements-dev.txt`.
Maybe we can add the following to try three times with a 2 second delay only for 

```python
@pytest.mark.flaky(reruns=3, reruns_delay=2, only_rerun=NNDCRequestError)
```

(Not sure on the only_rerun syntax - it might need to be a string or a list of exceptions.)

Fix patch:
diff --git a/requirements-dev.txt b/requirements-dev.txt
index fa475d2c..348d9e68 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -3,8 +3,9 @@ bump2version
 coverage
 flake8>=4.0.1
 pre-commit
-pytest<7.0.0
+pytest
 pytest-black
 pytest-cov
 pytest-rerunfailures
 pytest-xdist
+requests


Test patch:
diff --git a/tests/df_cache_test.py b/tests/df_cache_test.py
index 1396e7aa..1920a63c 100644
--- a/tests/df_cache_test.py
+++ b/tests/df_cache_test.py
@@ -61,6 +61,7 @@ def test_bad_path(self):
         """Test ExampleCache.check_path() exception for a bad path."""
         d = ExampleCache()
         d.path = "/bad/path"
+        d.filename = "/bad/path/filename.csv"
         with pytest.raises(CacheError):
             d.check_path()
         with pytest.raises(CacheError):
diff --git a/tests/materials_test.py b/tests/materials_test.py
index 6fe3a4fe..4baa6ff9 100644
--- a/tests/materials_test.py
+++ b/tests/materials_test.py
@@ -12,9 +12,11 @@
 import becquerel.tools.materials as materials
 import becquerel.tools.materials_compendium as materials_compendium
 import pytest
+from utils import xcom_is_up
 
 
 @pytest.mark.webtest
+@pytest.mark.skipif(not xcom_is_up(), reason="XCOM is down.")
 class TestConvertComposition:
     """Test convert_composition."""
 
@@ -51,6 +53,7 @@ def test_z_out_of_range(self):
 
 
 @pytest.mark.webtest
+@pytest.mark.skipif(not xcom_is_up(), reason="XCOM is down.")
 def test_materials():
     """Test fetch_materials."""
     fetch_materials()
@@ -58,6 +61,7 @@ def test_materials():
 
 
 @pytest.mark.webtest
+@pytest.mark.skipif(not xcom_is_up(), reason="XCOM is down.")
 def test_materials_force():
     """Test fetch_materials with force=True."""
     assert os.path.exists(materials.FILENAME)
@@ -87,6 +91,7 @@ def test_materials_dummy_csv():
 
 
 @pytest.mark.webtest
+@pytest.mark.skipif(not xcom_is_up(), reason="XCOM is down.")
 def test_materials_dummy_compendium_pre2022():
     """Test fetch_materials with a dummy Compendium JSON file.
 
@@ -132,6 +137,7 @@ def test_materials_dummy_compendium_pre2022():
 
 
 @pytest.mark.webtest
+@pytest.mark.skipif(not xcom_is_up(), reason="XCOM is down.")
 def test_materials_dummy_compendium_2022():
     """Test fetch_materials with a dummy Compendium JSON file.
 
@@ -186,6 +192,7 @@ def test_materials_dummy_compendium_2022():
 
 
 @pytest.mark.webtest
+@pytest.mark.skipif(not xcom_is_up(), reason="XCOM is down.")
 def test_materials_dummy_compendium_error():
     """Test fetch_materials with a dummy Compendium JSON file.
 
@@ -205,6 +212,7 @@ def test_materials_dummy_compendium_error():
 
 
 @pytest.mark.webtest
+@pytest.mark.skipif(not xcom_is_up(), reason="XCOM is down.")
 def test_materials_no_compendium():
     """Test fetch_materials with no Compendium JSON file."""
     # point to a dummy JSON file that does not exist
diff --git a/tests/nndc_test.py b/tests/nndc_test.py
index 2f66d2d7..73e818d6 100644
--- a/tests/nndc_test.py
+++ b/tests/nndc_test.py
@@ -3,6 +3,7 @@
 import numpy as np
 import pandas as pd
 from becquerel.tools import nndc
+from utils import nndc_is_up
 import pytest
 
 
@@ -137,7 +138,8 @@ def test_20(self):
             nndc._parse_float_uncertainty("7", "@")
 
 
-@pytest.mark.flaky(reruns=3)
+@pytest.mark.skipif(not nndc_is_up(), reason="NNDC is down.")
+@pytest.mark.flaky(reruns=10)
 class NNDCQueryTests:
     """Tests common to NNDCQuery-derived classes."""
 
@@ -559,7 +561,8 @@ def test_query_zrange_7_20_elevelrange_01_inf(self):
 
 
 @pytest.mark.webtest
-@pytest.mark.flaky(reruns=3)
+@pytest.mark.skipif(not nndc_is_up(), reason="NNDC is down.")
+@pytest.mark.flaky(reruns=10)
 class TestNuclearWalletCard(NNDCQueryTests):
     """Test NNDC nuclear_wallet_card query."""
 
@@ -703,7 +706,8 @@ def test_wallet_exception_too_many(self):
 
 
 @pytest.mark.webtest
-@pytest.mark.flaky(reruns=3)
+@pytest.mark.skipif(not nndc_is_up(), reason="NNDC is down.")
+@pytest.mark.flaky(reruns=10)
 class TestDecayRadiationQuery(NNDCQueryTests):
     """Test NNDC decay_radiation."""
 
diff --git a/tests/utils.py b/tests/utils.py
index 94b6fa76..754b5054 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -1,5 +1,30 @@
 """Helpers for testing."""
 from pathlib import Path
+import requests
 
 TESTS_PATH = Path(__file__).parent.absolute()
 REPO_PATH = TESTS_PATH.parent
+
+
+def database_is_up(url):
+    """Check whether an online database can be reached.
+
+    Parameters
+    ----------
+    url : str
+        URL to query.
+    """
+    try:
+        return requests.post(url).status_code == requests.codes.ok
+    except requests.exceptions.ConnectionError:
+        return False
+
+
+def nndc_is_up():
+    """Check whether the NNDC databases can be reached."""
+    return database_is_up("https://www.nndc.bnl.gov/nudat3/indx_sigma.jsp")
+
+
+def xcom_is_up():
+    """Check whether the NIST XCOM databases can be reached."""
+    return database_is_up("https://physics.nist.gov/PhysRefData/Xcom/html/xcom1.html")
diff --git a/tests/wallet_cache_test.py b/tests/wallet_cache_test.py
index ff353fef..e746f838 100644
--- a/tests/wallet_cache_test.py
+++ b/tests/wallet_cache_test.py
@@ -4,6 +4,7 @@
 import uncertainties
 import becquerel.tools.wallet_cache as wallet_cache
 import pytest
+from utils import nndc_is_up
 
 
 @pytest.mark.parametrize(
@@ -49,6 +50,7 @@ def test_format_ufloat(arg, result):
 
 @pytest.mark.webtest
 @pytest.mark.flaky(reruns=3)
+@pytest.mark.skipif(not nndc_is_up(), reason="NNDC is down.")
 class TestWalletCardCache:
     """Test functionality of wallet_cache."""
 
diff --git a/tests/xcom_test.py b/tests/xcom_test.py
index 51fb4fc2..99d8159a 100644
--- a/tests/xcom_test.py
+++ b/tests/xcom_test.py
@@ -2,6 +2,7 @@
 
 import numpy as np
 from becquerel.tools import xcom
+from utils import xcom_is_up
 import pytest
 
 XCOM_URL_ORIG = xcom._URL
@@ -122,6 +123,7 @@
 
 
 @pytest.mark.webtest
+@pytest.mark.skipif(not xcom_is_up(), reason="XCOM is down.")
 class TestFetchXCOMData:
     """Test fetch_xcom_data function."""