Repository: PlasmaPy/PlasmaPy

Issue:
Bring back parallel testing in tox.ini
I forgot a flag in there while debugging.

Discussion:
pep8speaks: Hello @StanczakDominik! Thanks for updating your pull request.



* In the file [`plasmapy/classes/sources/tests/test_openpmd_hdf5.py`](https://github.com/PlasmaPy/PlasmaPy/blob/c34a45506ac5d434a24ca71b69967fd7cd487ed7/plasmapy/classes/sources/tests/test_openpmd_hdf5.py):

> [Line 10:1](https://github.com/PlasmaPy/PlasmaPy/blob/c34a45506ac5d434a24ca71b69967fd7cd487ed7/plasmapy/classes/sources/tests/test_openpmd_hdf5.py#L10): [E302](https://duckduckgo.com/?q=pep8%20E302) expected 2 blank lines, found 1
> [Line 15:1](https://github.com/PlasmaPy/PlasmaPy/blob/c34a45506ac5d434a24ca71b69967fd7cd487ed7/plasmapy/classes/sources/tests/test_openpmd_hdf5.py#L15): [E302](https://duckduckgo.com/?q=pep8%20E302) expected 2 blank lines, found 1
> [Line 20:1](https://github.com/PlasmaPy/PlasmaPy/blob/c34a45506ac5d434a24ca71b69967fd7cd487ed7/plasmapy/classes/sources/tests/test_openpmd_hdf5.py#L20): [E302](https://duckduckgo.com/?q=pep8%20E302) expected 2 blank lines, found 1


StanczakDominik: I'm getting some rather weird test collection failures in `plasmapy.atomic.tests.test_ionization_states.py` - I'm starting to think rewriting those using fixtures, so that `pytest-xdist` can pick and choose which tests to send to which worker earlier, may solve it. I'm going to try doing that and see where that leads.
codecov[bot]: # [Codecov](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/664?src=pr&el=h1) Report
> Merging [#664](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/664?src=pr&el=desc) into [master](https://codecov.io/gh/PlasmaPy/PlasmaPy/commit/c0735461d3c1cb487fabdf60ed131275d2775ece?src=pr&el=desc) will **decrease** coverage by `0.05%`.
> The diff coverage is `50%`.

[![Impacted file tree graph](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/664/graphs/tree.svg?width=650&token=LzZ5mnXVad&height=150&src=pr)](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/664?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #664      +/-   ##
==========================================
- Coverage   95.15%   95.09%   -0.06%     
==========================================
  Files          55       55              
  Lines        4664     4670       +6     
==========================================
+ Hits         4438     4441       +3     
- Misses        226      229       +3
```


| [Impacted Files](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/664?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [plasmapy/classes/sources/openpmd\_hdf5.py](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/664/diff?src=pr&el=tree#diff-cGxhc21hcHkvY2xhc3Nlcy9zb3VyY2VzL29wZW5wbWRfaGRmNS5weQ==) | `89.53% <50%> (-2.97%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/664?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/664?src=pr&el=footer). Last update [c073546...0f0069b](https://codecov.io/gh/PlasmaPy/PlasmaPy/pull/664?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.travis.yml b/.travis.yml
index dcc2b83b..8fe7dcb9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,12 +22,12 @@ matrix:
     fast_finish: true
     include:
         - name: "3.7"
-          env: TOX_ENV='py37-offline'
+          env: TOX_ENV='py37'
           python: "3.7"
           stage: Generic tests
 
         - name: "3.6"
-          env: TOX_ENV='py36-offline'
+          env: TOX_ENV='py36'
           python: "3.6"
           stage: Cron tests
 
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 77e40595..7c298720 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -26,36 +26,36 @@ pr:
 jobs:
 - template: azure-templates.yml
   parameters:
-    name: Linux_37_offline_coverage
+    name: Linux_37_coverage
     os: linux
-    tox: py37-offline
+    tox: py37
 
 - template: azure-templates.yml
   parameters:
-    name: Linux_36_offline
+    name: Linux_36
     os: linux
-    tox: py36-offline 
+    tox: py36
 
 - template: azure-templates.yml
   parameters:
-    name: Windows_37_offline
+    name: Windows_37
     os: windows
-    tox: py37-offline 
+    tox: py37
 
 - template: azure-templates.yml
   parameters:
-    name: Windows_36_offline
+    name: Windows_36
     os: windows
-    tox: py36-offline 
+    tox: py36
 
 - template: azure-templates.yml
   parameters:
-    name: macOS_37_offline
+    name: macOS_37
     os: macos
-    tox: py37-offline 
+    tox: py37
 
 - template: azure-templates.yml
   parameters:
-    name: macOS_36_offline
+    name: macOS_36
     os: macos
-    tox: py36-offline 
+    tox: py36
diff --git a/plasmapy/classes/sources/openpmd_hdf5.py b/plasmapy/classes/sources/openpmd_hdf5.py
index 1dc04a23..cdbc31c3 100644
--- a/plasmapy/classes/sources/openpmd_hdf5.py
+++ b/plasmapy/classes/sources/openpmd_hdf5.py
@@ -84,6 +84,15 @@ def __init__(self, hdf5, **kwargs):
 
         self.subname = tuple(self.h5['data'])[0]
 
+    def __enter__(self):
+        return self.h5
+
+    def close(self):
+        self.h5.close()
+
+    def __exit__(self):
+        self.h5.close()
+
     def _check_valid_openpmd_version(self):
         try:
             openPMD_version = self.h5.attrs["openPMD"].decode('utf-8')
diff --git a/tox.ini b/tox.ini
index 30b2d0c3..36ba764c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
 [tox]
-envlist = py3{6,7}-{offline,astropydev,numpydev},build_docs
+envlist = py3{6,7}{,-astropydev,-numpydev},build_docs
 isolated_build = True
 
 [testenv]
@@ -9,15 +9,14 @@ whitelist_externals=
 changedir = tmp
 setenv =
     MPLBACKEND = agg
-    PYTEST_COMMAND = pytest -vvv --pyargs plasmapy --cov=plasmapy --cov-config={toxinidir}/setup.cfg --durations=25 {toxinidir}/docs
+    PYTEST_COMMAND = pytest -vvv --pyargs plasmapy --cov=plasmapy --cov-config={toxinidir}/setup.cfg --durations=25 {toxinidir}/docs -n=auto --dist=loadfile
 extras = all,tests
 deps =
     numpydev: git+https://github.com/numpy/numpy
     astropydev: git+https://github.com/astropy/astropy
     pytest-cov
     pytest-xdist
-commands =
-    offline,astropydev,numpydev: {env:PYTEST_COMMAND} {posargs}
+commands = {env:PYTEST_COMMAND} {posargs}
 
 [testenv:build_docs]
 basepython = python3.7


Test patch:
diff --git a/plasmapy/classes/sources/tests/test_openpmd_hdf5.py b/plasmapy/classes/sources/tests/test_openpmd_hdf5.py
index 9fa2357e..35fd19fd 100644
--- a/plasmapy/classes/sources/tests/test_openpmd_hdf5.py
+++ b/plasmapy/classes/sources/tests/test_openpmd_hdf5.py
@@ -1,39 +1,56 @@
 from plasmapy.classes.sources import openpmd_hdf5
 from plasmapy.classes.exceptions import DataStandardError
 from plasmapy.data.test import rootdir
+import plasmapy.classes
 
 from astropy import units as u
 from typing import Union, Tuple, List
 import os
 import pytest
 
+@pytest.fixture(scope='module')
+def h5_2d(request):
+    h5 = openpmd_hdf5.HDF5Reader(hdf5=os.path.join(rootdir, "data00000255.h5"))
+    yield h5
+    h5.close()
+
+@pytest.fixture(scope='module')
+def h5_3d(request):
+    h5 = openpmd_hdf5.HDF5Reader(hdf5=os.path.join(rootdir, "data00000100.h5"))
+    yield h5
+    h5.close()
+
+@pytest.fixture(scope='module')
+def h5_theta(request):
+    h5 = openpmd_hdf5.HDF5Reader(hdf5=os.path.join(rootdir, "data00000200.h5"))
+    yield h5
+    h5.close()
 
 class TestOpenPMD2D:
     """Test 2D HDF5 dataset based on OpenPMD."""
     # Downloaded from
     # https://github.com/openPMD/openPMD-example-datasets/blob/draft/example-2d.tar.gz
     # per the Creative Commons Zero v1.0 Universal license
-    h5 = openpmd_hdf5.HDF5Reader(hdf5=os.path.join(rootdir, "data00000255.h5"))
 
-    def test_has_electric_field_with_units(self):
-        assert isinstance(self.h5.electric_field.to(u.V / u.m), u.Quantity)
+    def test_has_electric_field_with_units(self, h5_2d):
+        assert isinstance(h5_2d.electric_field.to(u.V / u.m), u.Quantity)
 
-    def test_correct_shape_electric_field(self):
-        assert self.h5.electric_field.shape == (3, 51, 201)
+    def test_correct_shape_electric_field(self, h5_2d):
+        assert h5_2d.electric_field.shape == (3, 51, 201)
 
-    def test_has_charge_density_with_units(self):
-        assert self.h5.charge_density.to(u.C / u.m**3)
+    def test_has_charge_density_with_units(self, h5_2d):
+        assert h5_2d.charge_density.to(u.C / u.m**3)
 
-    def test_correct_shape_charge_density(self):
-        assert self.h5.charge_density.shape == (51, 201)
+    def test_correct_shape_charge_density(self, h5_2d):
+        assert h5_2d.charge_density.shape == (51, 201)
 
-    def test_has_magnetic_field(self):
+    def test_has_magnetic_field(self, h5_2d):
         with pytest.raises(AttributeError):
-            self.h5.magnetic_field
+            h5_2d.magnetic_field
 
-    def test_has_electric_current(self):
+    def test_has_electric_current(self, h5_2d):
         with pytest.raises(AttributeError):
-            self.h5.electric_current
+            h5_2d.electric_current
 
 
 class TestOpenPMD3D:
@@ -41,27 +58,26 @@ class TestOpenPMD3D:
     # Downloaded from
     # https://github.com/openPMD/openPMD-example-datasets/blob/draft/example-3d.tar.gz
     # per the Creative Commons Zero v1.0 Universal license
-    h5 = openpmd_hdf5.HDF5Reader(hdf5=os.path.join(rootdir, "data00000100.h5"))
 
-    def test_has_electric_field_with_units(self):
-        assert isinstance(self.h5.electric_field.to(u.V / u.m), u.Quantity)
+    def test_has_electric_field_with_units(self, h5_3d):
+        assert isinstance(h5_3d.electric_field.to(u.V / u.m), u.Quantity)
 
-    def test_correct_shape_electric_field(self):
-        assert self.h5.electric_field.shape == (3, 26, 26, 201)
+    def test_correct_shape_electric_field(self, h5_3d):
+        assert h5_3d.electric_field.shape == (3, 26, 26, 201)
 
-    def test_has_charge_density_with_units(self):
-        assert isinstance(self.h5.charge_density.to(u.C / u.m**3), u.Quantity)
+    def test_has_charge_density_with_units(self, h5_3d):
+        assert isinstance(h5_3d.charge_density.to(u.C / u.m**3), u.Quantity)
 
-    def test_correct_shape_charge_density(self):
-        assert self.h5.charge_density.shape == (26, 26, 201)
+    def test_correct_shape_charge_density(self, h5_3d):
+        assert h5_3d.charge_density.shape == (26, 26, 201)
 
-    def test_has_magnetic_field(self):
+    def test_has_magnetic_field(self, h5_3d):
         with pytest.raises(AttributeError):
-            self.h5.magnetic_field
+            h5_3d.magnetic_field
 
-    def test_has_electric_current(self):
+    def test_has_electric_current(self, h5_3d):
         with pytest.raises(AttributeError):
-            self.h5.electric_current
+            h5_3d.electric_current
 
 
 class TestOpenPMDThetaMode:
@@ -69,31 +85,30 @@ class TestOpenPMDThetaMode:
     # Downloaded from
     # https://github.com/openPMD/openPMD-example-datasets/blob/draft/example-thetaMode.tar.gz
     # per the Creative Commons Zero v1.0 Universal license
-    h5 = openpmd_hdf5.HDF5Reader(hdf5=os.path.join(rootdir, "data00000200.h5"))
 
-    def test_has_electric_field_with_units(self):
-        assert isinstance(self.h5.electric_field.to(u.V / u.m), u.Quantity)
+    def test_has_electric_field_with_units(self, h5_theta):
+        assert isinstance(h5_theta.electric_field.to(u.V / u.m), u.Quantity)
 
-    def test_correct_shape_electric_field(self):
-        assert self.h5.electric_field.shape == (3, 3, 51, 201)
+    def test_correct_shape_electric_field(self, h5_theta):
+        assert h5_theta.electric_field.shape == (3, 3, 51, 201)
 
-    def test_has_charge_density_with_units(self):
-        assert isinstance(self.h5.charge_density.to(u.C / u.m**3), u.Quantity)
+    def test_has_charge_density_with_units(self, h5_theta):
+        assert isinstance(h5_theta.charge_density.to(u.C / u.m**3), u.Quantity)
 
-    def test_correct_shape_charge_density(self):
-        assert self.h5.charge_density.shape == (3, 51, 201)
+    def test_correct_shape_charge_density(self, h5_theta):
+        assert h5_theta.charge_density.shape == (3, 51, 201)
 
-    def test_has_magnetic_field_with_units(self):
-        assert isinstance(self.h5.magnetic_field.to(u.T), u.Quantity)
+    def test_has_magnetic_field_with_units(self, h5_theta):
+        assert isinstance(h5_theta.magnetic_field.to(u.T), u.Quantity)
 
-    def test_correct_shape_magnetic_field(self):
-        assert self.h5.magnetic_field.shape == (3, 3, 51, 201)
+    def test_correct_shape_magnetic_field(self, h5_theta):
+        assert h5_theta.magnetic_field.shape == (3, 3, 51, 201)
 
-    def test_has_electric_current_with_units(self):
-        assert isinstance(self.h5.electric_current.to(u.A * u.kg / u.m**3), u.Quantity)
+    def test_has_electric_current_with_units(self, h5_theta):
+        assert isinstance(h5_theta.electric_current.to(u.A * u.kg / u.m**3), u.Quantity)
 
-    def test_correct_shape_electric_current(self):
-        assert self.h5.electric_current.shape == (3, 3, 51, 201)
+    def test_correct_shape_electric_current(self, h5_theta):
+        assert h5_theta.electric_current.shape == (3, 3, 51, 201)
 
 
 units_test_table = [
@@ -122,3 +137,7 @@ def test_unavailable_hdf5():
 def test_non_openpmd_hdf5():
     with pytest.raises(DataStandardError):
         openpmd_hdf5.HDF5Reader(hdf5=os.path.join(rootdir, "blank.h5"))
+
+def test_HDF5Reader(h5_2d):
+    assert isinstance(h5_2d, plasmapy.classes.sources.HDF5Reader)
+    assert isinstance(h5_2d, plasmapy.classes.BasePlasma)
diff --git a/plasmapy/classes/tests/test_plasma_factory.py b/plasmapy/classes/tests/test_plasma_factory.py
index af37cd0d..6f5af036 100644
--- a/plasmapy/classes/tests/test_plasma_factory.py
+++ b/plasmapy/classes/tests/test_plasma_factory.py
@@ -1,10 +1,16 @@
 import astropy.units as u
 import numpy as np
 import os
+import pytest
 
 import plasmapy.classes
 from plasmapy.data.test import rootdir
 
+@pytest.fixture(scope='module')
+def h5(request):
+    h5 = plasmapy.classes.Plasma(hdf5=os.path.join(rootdir, "data00000255.h5"))
+    yield h5
+    h5.close()
 
 class TestPlasma(object):
     def test_patters(self):
@@ -32,8 +38,3 @@ def test_PlasmaBlob(self):
                                        particle=particle)
         assert isinstance(blob, plasmapy.classes.sources.PlasmaBlob)
         assert isinstance(blob, plasmapy.classes.BasePlasma)
-
-    def test_HDF5Reader(self):
-        h5 = plasmapy.classes.Plasma(hdf5=os.path.join(rootdir, "data00000255.h5"))
-        assert isinstance(h5, plasmapy.classes.sources.HDF5Reader)
-        assert isinstance(h5, plasmapy.classes.BasePlasma)