Repository: sunpy/sunpy

Issue:
tests create figure hashes directory
when running `py.tests` on single file it creates unneeded tmp directories.



Discussion:
slightly off topic, this now supports having remote images to compare: https://github.com/matplotlib/pytest-mpl


Resolving patch:
diff --git a/sunpy/conftest.py b/sunpy/conftest.py
--- a/sunpy/conftest.py
+++ b/sunpy/conftest.py
@@ -22,9 +22,8 @@
 
 from astropy.tests import disable_internet
 
-from sunpy.tests import hash
-
-hash_library_original_len = len(hash.hash_library)
+from sunpy.tests.hash import HASH_LIBRARY_NAME
+from sunpy.tests.helpers import new_hash_library, figure_test_pngfiles
 
 GOOGLE_URL = 'http://www.google.com'
 
@@ -61,31 +60,17 @@ def pytest_runtest_teardown(item, nextitem):
 
 
 def pytest_unconfigure(config):
-    tempdir = tempfile.mkdtemp(suffix="_figures")
-    # the hash_library is indexed by the name of the test but we want to look
-    # things up with the hash value
-    inv_hash_library = {v: k for k, v in hash.hash_library.items()}
-
-    for h in hash.file_list:
-        test_name = inv_hash_library.get(h, '')
-        if test_name != '':
-            os.rename(hash.file_list[h], os.path.join(tempdir,
-                                                      test_name + '.png'))
-    print('All test files for figure hashes can be found in {0}'.format(
-        tempdir))
-
-    # Check if additions have been made to the hash library
-    if len(hash.hash_library) > hash_library_original_len:
+    if len(figure_test_pngfiles) > 0:
+        tempdir = tempfile.mkdtemp(suffix="_figures")
+
+        # Rename each PNG with the name of the corresponding test
+        for test_name in figure_test_pngfiles:
+            os.rename(figure_test_pngfiles[test_name], os.path.join(tempdir, test_name + '.png'))
+
         # Write the new hash library in JSON
-        tempdir = tempfile.mkdtemp()
-        hashfile = os.path.join(tempdir, hash.HASH_LIBRARY_NAME)
-        with open(hashfile, 'wb') as outfile:
-            json.dump(
-                hash.hash_library,
-                outfile,
-                sort_keys=True,
-                indent=4,
-                separators=(',', ': '))
-        print(
-            "The hash library has expanded and should be copied to sunpy/tests/")
-        print("  " + hashfile)
+        hashfile = os.path.join(tempdir, HASH_LIBRARY_NAME)
+        with open(hashfile, 'w') as outfile:
+            json.dump(new_hash_library, outfile, sort_keys=True, indent=4, separators=(',', ': '))
+
+        print('All test files for figure hashes can be found in {0}'.format(tempdir))
+        print("The corresponding hash library is {0}".format(hashfile))


Test patch:
diff --git a/sunpy/tests/figure_hashes.json b/sunpy/tests/figure_hashes_py2.json
similarity index 100%
rename from sunpy/tests/figure_hashes.json
rename to sunpy/tests/figure_hashes_py2.json
diff --git a/sunpy/tests/figure_hashes_py3.json b/sunpy/tests/figure_hashes_py3.json
new file mode 100644
--- /dev/null
+++ b/sunpy/tests/figure_hashes_py3.json
@@ -0,0 +1,15 @@
+{
+    "sunpy.map.tests.test_mapbase.test_draw_contours_aia": "dca65969fd7a795dbc0012fa8a7cc64307d9d0fc76e5d0baf3d23804a8fd0c2e",
+    "sunpy.map.tests.test_mapbase.test_peek_aia171": "57d3598874f8abfff9de004be52b6840a2521610189ef3b6932bb02b0d3067e5",
+    "sunpy.map.tests.test_mapbase.test_peek_grid_aia171": "9f6f0190922b565cc660592e12c249a63447c4032850dd817cbb6b3a95641481",
+    "sunpy.map.tests.test_mapbase.test_peek_grid_limb_aia171": "29e39527d21e15513ec2e7b3f37cc16381f482c06f7cd6dfe33cd5de09cfe7b9",
+    "sunpy.map.tests.test_mapbase.test_peek_limb_aia171": "82dc4759b98205d86a3d0b23afedf25592bc744ddd84ea914f1a81a0abd8e4aa",
+    "sunpy.map.tests.test_mapbase.test_plot_aia171": "afa3b518e455556450aedb67673f42d3e78b5af6d0f7817e509a2afbd8347aad",
+    "sunpy.map.tests.test_mapbase.test_plot_aia171_nowcsaxes": "d564a8137b7dec0fb322fe07012c661fe5ef824a0b0974c9baa3f95f93d78fe5",
+    "sunpy.map.tests.test_mapbase.test_plot_aia171_superpixel": "4cf060df8b2ef0c0998010183557b8e98ae559b260eee570df2b0ff0000a51b3",
+    "sunpy.map.tests.test_mapbase.test_plot_aia171_superpixel_nowcsaxes": "ea53e7482797fc6a76ccb345c87a46a051565670e38037e6341e4cf8a7a20448",
+    "sunpy.map.tests.test_mapbase.test_plot_masked_aia171": "8754129e5fa0ad7d97880144872aca0d7a918a20a066a4c7a17de35fd3478bff",
+    "sunpy.map.tests.test_mapbase.test_plot_masked_aia171_nowcsaxes": "b6f299163129d96690d39a8b6a17131898cfd911893e62ba6033418c55253c9c",
+    "sunpy.map.tests.test_mapbase.test_plot_masked_aia171_superpixel": "0febcd3d1ef014cde7625a635ed7bfa860b84917ab35acc988fdaa368aaa2cdb",
+    "sunpy.map.tests.test_mapbase.test_plot_masked_aia171_superpixel_nowcsaxes": "7a0d6a0f012587d5c28c9b4a7a25dc309e3e09f61d8cc9491405a179ac54bea2"
+}
\ No newline at end of file
diff --git a/sunpy/tests/hash.py b/sunpy/tests/hash.py
--- a/sunpy/tests/hash.py
+++ b/sunpy/tests/hash.py
@@ -1,13 +1,13 @@
 from __future__ import absolute_import, division, print_function
+from sys import version_info
 import os
 import io
 import hashlib
 import json
-import tempfile
 
 import matplotlib.pyplot as plt
 
-HASH_LIBRARY_NAME = 'figure_hashes.json'
+HASH_LIBRARY_NAME = 'figure_hashes_py{0}.json'.format(version_info.major)
 
 # Load the hash library if it exists
 try:
@@ -16,9 +16,8 @@
 except IOError:
     hash_library = {}
 
-file_list = {}
 
-def hash_figure(figure=None):
+def hash_figure(figure=None, out_stream=None):
     """
     For a matplotlib.figure.Figure, returns the SHA256 hash as a hexadecimal string.
 
@@ -27,6 +26,9 @@ def hash_figure(figure=None):
     figure : matplotlib.figure.Figure
         If None is specified, the current figure is used (as determined by matplotlib.pyplot.gcf())
 
+    out_stream : I/O stream (e.g., an open file)
+        If not None, write a PNG of the figure to the stream
+
     Returns
     -------
     out : string
@@ -36,18 +38,23 @@ def hash_figure(figure=None):
     if figure is None:
         figure = plt.gcf()
 
-    imgdata = tempfile.NamedTemporaryFile(delete=False)
+    if out_stream is None:
+        imgdata = io.BytesIO()
+    else:
+        imgdata = out_stream
+
     figure.savefig(imgdata, format='png')
 
     imgdata.seek(0)
     buf = imgdata.read()
-    imgdata.close()
+    if out_stream is None:
+        imgdata.close()
 
     hasher = hashlib.sha256()
     hasher.update(buf)
-    file_list[hasher.hexdigest()] = imgdata.name
     return hasher.hexdigest()
 
+
 def verify_figure_hash(name, figure=None):
     """
     Verifies whether a figure has the same hash as the named hash in the current hash library.
diff --git a/sunpy/tests/helpers.py b/sunpy/tests/helpers.py
--- a/sunpy/tests/helpers.py
+++ b/sunpy/tests/helpers.py
@@ -4,6 +4,8 @@
 
 from __future__ import absolute_import, division, print_function
 import warnings
+import tempfile
+import platform
 
 import pytest
 import numpy as np
@@ -30,7 +32,6 @@
         SKIP_GLYMUR = True
 
 # Skip ana tests if we are on Windows or we can't import the c extension.
-import platform
 if platform.system() == 'Windows':
     SKIP_ANA = True
 else:
@@ -59,6 +60,7 @@
 
 skip_wcsaxes = pytest.mark.skipif(SKIP_WCSAXES, reason="wcsaxes is not available")
 
+
 @pytest.fixture
 def warnings_as_errors(request):
     warnings.simplefilter('error')
@@ -100,10 +102,17 @@ def assert_quantity_allclose(actual, desired, rtol=1.e-7, atol=0, err_msg='', ve
                                    rtol=rtol, atol=atol, err_msg=err_msg, verbose=verbose)
 
 
+new_hash_library = {}
+
+figure_test_pngfiles = {}
+
+
 def figure_test(test_function):
     """
     A decorator for a test that verifies the hash of the current figure or the returned figure,
     with the name of the test function as the hash identifier in the library.
+    A PNG is also created with a temporary filename, with the lookup stored in the
+    `figure_test_pngfiles` dictionary.
 
     All such decorated tests are marked with `pytest.mark.figure` for convenient filtering.
 
@@ -118,9 +127,12 @@ def test_simple_plot():
     def wrapper(*args, **kwargs):
         plt.figure()
         name = "{0}.{1}".format(test_function.__module__, test_function.__name__)
-        figure_hash = hash.hash_figure(test_function(*args, **kwargs))
+        pngfile = tempfile.NamedTemporaryFile(delete=False)
+        figure_hash = hash.hash_figure(test_function(*args, **kwargs), out_stream=pngfile)
+        figure_test_pngfiles[name] = pngfile.name
+        pngfile.close()
+        new_hash_library[name] = figure_hash
         if name not in hash.hash_library:
-            hash.hash_library[name] = figure_hash
             pytest.fail("Hash not present: {0}".format(name))
         else:
             assert hash.hash_library[name] == figure_hash
diff --git a/sunpy/tests/setup_package.py b/sunpy/tests/setup_package.py
--- a/sunpy/tests/setup_package.py
+++ b/sunpy/tests/setup_package.py
@@ -1,3 +1,3 @@
 def get_package_data():
     return {
-        _ASTROPY_PACKAGE_NAME_ + '.tests': ['coveragerc', 'figure_hashes.json']}
+        _ASTROPY_PACKAGE_NAME_ + '.tests': ['coveragerc', 'figure_hashes_py2.json', 'figure_hashes_py3.json']}