Blob Blame History Raw
From 756c9af63aee1785da91b218633a2c2fdfa09f11 Mon Sep 17 00:00:00 2001
From: Phil Elson <pelson.pub@gmail.com>
Date: Fri, 23 Feb 2018 06:55:16 +0000
Subject: [PATCH 1/7] Merge pull request #1032 from QuLogic/test-updates

Improvements to test metadata

Signed-off-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>
---
 lib/cartopy/_epsg.py                          |  5 +++--
 lib/cartopy/tests/io/test_downloaders.py      |  4 +++-
 lib/cartopy/tests/io/test_ogc_clients.py      |  5 ++++-
 lib/cartopy/tests/io/test_srtm.py             |  5 +++--
 lib/cartopy/tests/mpl/test_caching.py         |  3 +++
 lib/cartopy/tests/mpl/test_crs.py             |  5 ++++-
 lib/cartopy/tests/mpl/test_examples.py        |  2 ++
 lib/cartopy/tests/mpl/test_features.py        |  5 ++++-
 lib/cartopy/tests/mpl/test_gridliner.py       |  4 +++-
 lib/cartopy/tests/mpl/test_images.py          |  8 ++++++-
 lib/cartopy/tests/mpl/test_img_transform.py   |  3 ++-
 lib/cartopy/tests/mpl/test_mpl_integration.py | 31 ++++++++++++++++++++++++++-
 lib/cartopy/tests/mpl/test_shapely_to_mpl.py  |  5 ++++-
 lib/cartopy/tests/mpl/test_ticks.py           |  7 +++++-
 lib/cartopy/tests/mpl/test_web_services.py    |  4 +++-
 lib/cartopy/tests/test_coastline.py           |  9 +++++---
 lib/cartopy/tests/test_coding_standards.py    |  6 ++++--
 lib/cartopy/tests/test_crs.py                 |  3 ++-
 lib/cartopy/tests/test_img_nest.py            |  4 +++-
 lib/cartopy/tests/test_img_tiles.py           |  3 ++-
 lib/cartopy/tests/test_shapereader.py         | 19 ++++++++--------
 lib/cartopy/util.py                           | 31 ++++++++++++++++-----------
 22 files changed, 127 insertions(+), 44 deletions(-)

diff --git a/lib/cartopy/_epsg.py b/lib/cartopy/_epsg.py
index 8444d47..65c03de 100644
--- a/lib/cartopy/_epsg.py
+++ b/lib/cartopy/_epsg.py
@@ -21,11 +21,11 @@ Provide support for converting EPSG codes to Projection instances.
 
 from __future__ import (absolute_import, division, print_function)
 
-import cartopy.crs as ccrs
 import numpy as np
-import pyepsg
 import shapely.geometry as sgeom
 
+import cartopy.crs as ccrs
+
 
 _GLOBE_PARAMS = {'datum': 'datum',
                  'ellps': 'ellipse',
@@ -39,6 +39,7 @@ _GLOBE_PARAMS = {'datum': 'datum',
 
 class _EPSGProjection(ccrs.Projection):
     def __init__(self, code):
+        import pyepsg
         projection = pyepsg.get(code)
         if not isinstance(projection, pyepsg.ProjectedCRS):
             raise ValueError('EPSG code does not define a projection')
diff --git a/lib/cartopy/tests/io/test_downloaders.py b/lib/cartopy/tests/io/test_downloaders.py
index 4fade4d..c935b41 100644
--- a/lib/cartopy/tests/io/test_downloaders.py
+++ b/lib/cartopy/tests/io/test_downloaders.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2011 - 2017, Met Office
+# (C) British Crown Copyright 2011 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -107,6 +107,7 @@ def test_from_config():
         assert r is land_downloader
 
 
+@pytest.mark.network
 def test_downloading_simple_ascii(download_to_temp):
     # downloads a file from the Google APIs. (very high uptime and file will
     # always be there - if this goes down, most of the internet would break!)
@@ -139,6 +140,7 @@ def test_downloading_simple_ascii(download_to_temp):
     assert counter.count == 0, 'Item was re-downloaded.'
 
 
+@pytest.mark.network
 def test_natural_earth_downloader(tmpdir):
     # downloads a file to a temporary location, and uses that temporary
     # location, then:
diff --git a/lib/cartopy/tests/io/test_ogc_clients.py b/lib/cartopy/tests/io/test_ogc_clients.py
index 1dc7048..b1d0f3c 100644
--- a/lib/cartopy/tests/io/test_ogc_clients.py
+++ b/lib/cartopy/tests/io/test_ogc_clients.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2011 - 2017, Met Office
+# (C) British Crown Copyright 2011 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -41,6 +41,7 @@ from cartopy.io.ogc_clients import _OWSLIB_AVAILABLE
 RESOLUTION = (30, 30)
 
 
+@pytest.mark.network
 @pytest.mark.skipif(not _OWSLIB_AVAILABLE, reason='OWSLib is unavailable.')
 class TestWMSRasterSource(object):
     URI = 'http://vmap0.tiles.osgeo.org/wms/vmap0'
@@ -144,6 +145,7 @@ class TestWMSRasterSource(object):
         assert img.shape == (40, 20, 4)
 
 
+@pytest.mark.network
 @pytest.mark.skipif(not _OWSLIB_AVAILABLE, reason='OWSLib is unavailable.')
 class TestWMTSRasterSource(object):
     URI = 'https://map1c.vis.earthdata.nasa.gov/wmts-geo/wmts.cgi'
@@ -217,6 +219,7 @@ class TestWMTSRasterSource(object):
         assert im2.extent == extent
 
 
+@pytest.mark.network
 @pytest.mark.skipif(not _OWSLIB_AVAILABLE, reason='OWSLib is unavailable.')
 class TestWFSGeometrySource(object):
     URI = 'https://nsidc.org/cgi-bin/atlas_south?service=WFS'
diff --git a/lib/cartopy/tests/io/test_srtm.py b/lib/cartopy/tests/io/test_srtm.py
index e998dfc..2f255d2 100644
--- a/lib/cartopy/tests/io/test_srtm.py
+++ b/lib/cartopy/tests/io/test_srtm.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2011 - 2017, Met Office
+# (C) British Crown Copyright 2011 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -27,7 +27,8 @@ import cartopy.crs as ccrs
 import cartopy.io.srtm
 
 
-pytestmark = pytest.mark.skip('SRTM login not supported')
+pytestmark = [pytest.mark.skip('SRTM login not supported'),
+              pytest.mark.network]
 
 
 class TestRetrieve(object):
diff --git a/lib/cartopy/tests/mpl/test_caching.py b/lib/cartopy/tests/mpl/test_caching.py
index f8da599..dbc367f 100644
--- a/lib/cartopy/tests/mpl/test_caching.py
+++ b/lib/cartopy/tests/mpl/test_caching.py
@@ -76,6 +76,7 @@ class CallCounter(object):
         setattr(self.parent, self.function_name, self.orig_fn)
 
 
+@pytest.mark.natural_earth
 def test_coastline_loading_cache():
     # a5caae040ee11e72a62a53100fe5edc355304419 added coastline caching.
     # This test ensures it is working.
@@ -99,6 +100,7 @@ def test_coastline_loading_cache():
     plt.close()
 
 
+@pytest.mark.natural_earth
 def test_shapefile_transform_cache():
     # a5caae040ee11e72a62a53100fe5edc355304419 added shapefile mpl
     # geometry caching based on geometry object id. This test ensures
@@ -185,6 +187,7 @@ def test_contourf_transform_path_counting():
     plt.close()
 
 
+@pytest.mark.network
 @pytest.mark.skipif(not _OWSLIB_AVAILABLE, reason='OWSLib is unavailable.')
 def test_wmts_tile_caching():
     image_cache = WMTSRasterSource._shared_image_cache
diff --git a/lib/cartopy/tests/mpl/test_crs.py b/lib/cartopy/tests/mpl/test_crs.py
index d0099fa..9076e38 100644
--- a/lib/cartopy/tests/mpl/test_crs.py
+++ b/lib/cartopy/tests/mpl/test_crs.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2013 - 2017, Met Office
+# (C) British Crown Copyright 2013 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -18,11 +18,13 @@
 from __future__ import (absolute_import, division, print_function)
 
 import matplotlib.pyplot as plt
+import pytest
 
 import cartopy.crs as ccrs
 from cartopy.tests.mpl import ImageTesting
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['lambert_conformal_south'])
 def test_lambert_south():
     # Reference image: http://www.icsm.gov.au/mapping/map_projections.html
@@ -33,6 +35,7 @@ def test_lambert_south():
     ax.gridlines()
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['mercator_squashed'])
 def test_mercator_squashed():
     globe = ccrs.Globe(semimajor_axis=10000, semiminor_axis=9000,
diff --git a/lib/cartopy/tests/mpl/test_examples.py b/lib/cartopy/tests/mpl/test_examples.py
index 59fd1f5..db689e0 100644
--- a/lib/cartopy/tests/mpl/test_examples.py
+++ b/lib/cartopy/tests/mpl/test_examples.py
@@ -18,6 +18,7 @@
 from __future__ import (absolute_import, division, print_function)
 
 import matplotlib.pyplot as plt
+import pytest
 
 from cartopy.tests.mpl import MPL_VERSION, ImageTesting
 
@@ -40,6 +41,7 @@ class ExampleImageTesting(ImageTesting):
         return new_fn
 
 
+@pytest.mark.natural_earth
 @ExampleImageTesting(['global_map'],
                      tolerance=4 if MPL_VERSION < '2' else 0)
 def test_global_map():
diff --git a/lib/cartopy/tests/mpl/test_features.py b/lib/cartopy/tests/mpl/test_features.py
index c675a3b..250e3db 100644
--- a/lib/cartopy/tests/mpl/test_features.py
+++ b/lib/cartopy/tests/mpl/test_features.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2011 - 2017, Met Office
+# (C) British Crown Copyright 2011 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -27,6 +27,7 @@ from cartopy.io.ogc_clients import _OWSLIB_AVAILABLE
 from cartopy.tests.mpl import MPL_VERSION, ImageTesting
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['natural_earth'])
 def test_natural_earth():
     ax = plt.axes(projection=ccrs.PlateCarree())
@@ -40,6 +41,7 @@ def test_natural_earth():
     ax.set_ylim((-40, 40))
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['natural_earth_custom'])
 def test_natural_earth_custom():
     ax = plt.axes(projection=ccrs.PlateCarree())
@@ -65,6 +67,7 @@ def test_gshhs():
                                          facecolor='green'), facecolor='blue')
 
 
+@pytest.mark.network
 @pytest.mark.skipif(not _OWSLIB_AVAILABLE, reason='OWSLib is unavailable.')
 @ImageTesting(['wfs'])
 def test_wfs():
diff --git a/lib/cartopy/tests/mpl/test_gridliner.py b/lib/cartopy/tests/mpl/test_gridliner.py
index d59e639..9fbeba7 100644
--- a/lib/cartopy/tests/mpl/test_gridliner.py
+++ b/lib/cartopy/tests/mpl/test_gridliner.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2011 - 2017, Met Office
+# (C) British Crown Copyright 2011 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -33,6 +33,7 @@ from cartopy.mpl.gridliner import LATITUDE_FORMATTER, LONGITUDE_FORMATTER
 from cartopy.tests.mpl import MPL_VERSION, ImageTesting
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['gridliner1'])
 def test_gridliner():
     ny, nx = 2, 4
@@ -112,6 +113,7 @@ else:
     grid_label_image = 'gridliner_labels_pre_mpl_1.5'
 
 
+@pytest.mark.natural_earth
 @ImageTesting([grid_label_image])
 def test_grid_labels():
     plt.figure(figsize=(8, 10))
diff --git a/lib/cartopy/tests/mpl/test_images.py b/lib/cartopy/tests/mpl/test_images.py
index b705c8d..105ee80 100644
--- a/lib/cartopy/tests/mpl/test_images.py
+++ b/lib/cartopy/tests/mpl/test_images.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2011 - 2017, Met Office
+# (C) British Crown Copyright 2011 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -23,6 +23,7 @@ import types
 import numpy as np
 import matplotlib.pyplot as plt
 from PIL import Image
+import pytest
 import shapely.geometry as sgeom
 
 from cartopy import config
@@ -43,6 +44,8 @@ REGIONAL_IMG = os.path.join(config['repo_data_dir'], 'raster', 'sample',
 # We have an exceptionally large tolerance for the web_tiles test.
 # The basemap changes on a regular basis (for seasons) and we really only
 # care that it is putting images onto the map which are roughly correct.
+@pytest.mark.natural_earth
+@pytest.mark.network
 @ImageTesting(['web_tiles'],
               tolerance=12 if MPL_VERSION < '2' else 2.9)
 def test_web_tiles():
@@ -78,6 +81,8 @@ def test_web_tiles():
     ax.coastlines()
 
 
+@pytest.mark.natural_earth
+@pytest.mark.network
 @ImageTesting(['image_merge'],
               tolerance=3.6 if MPL_VERSION < '2' else 0)
 def test_image_merge():
@@ -118,6 +123,7 @@ def test_imshow():
               extent=[-180, 180, -90, 90])
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['imshow_regional_projected'],
               tolerance=10.4 if MPL_VERSION < '2' else 0)
 def test_imshow_projected():
diff --git a/lib/cartopy/tests/mpl/test_img_transform.py b/lib/cartopy/tests/mpl/test_img_transform.py
index d966cc8..3b53c0a 100644
--- a/lib/cartopy/tests/mpl/test_img_transform.py
+++ b/lib/cartopy/tests/mpl/test_img_transform.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2011 - 2017, Met Office
+# (C) British Crown Copyright 2011 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -101,6 +101,7 @@ else:
     regrid_tolerance = 0
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['regrid_image'],
               tolerance=regrid_tolerance)
 def test_regrid_image():
diff --git a/lib/cartopy/tests/mpl/test_mpl_integration.py b/lib/cartopy/tests/mpl/test_mpl_integration.py
index 1fe91f8..681c9bc 100644
--- a/lib/cartopy/tests/mpl/test_mpl_integration.py
+++ b/lib/cartopy/tests/mpl/test_mpl_integration.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2011 - 2017, Met Office
+# (C) British Crown Copyright 2011 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -23,6 +23,7 @@ import warnings
 
 import numpy as np
 import matplotlib.pyplot as plt
+import pytest
 import six
 
 import cartopy.crs as ccrs
@@ -41,6 +42,7 @@ else:
     _STREAMPLOT_IMAGE = 'streamplot_pre_mpl_1.4.3'
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['global_contour_wrap'])
 def test_global_contour_wrap_new_transform():
     ax = plt.axes(projection=ccrs.PlateCarree())
@@ -50,6 +52,7 @@ def test_global_contour_wrap_new_transform():
     plt.contour(x, y, data, transform=ccrs.PlateCarree())
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['global_contour_wrap'])
 def test_global_contour_wrap_no_transform():
     ax = plt.axes(projection=ccrs.PlateCarree())
@@ -59,6 +62,7 @@ def test_global_contour_wrap_no_transform():
     plt.contour(x, y, data)
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['global_contourf_wrap'])
 def test_global_contourf_wrap_new_transform():
     ax = plt.axes(projection=ccrs.PlateCarree())
@@ -68,6 +72,7 @@ def test_global_contourf_wrap_new_transform():
     plt.contourf(x, y, data, transform=ccrs.PlateCarree())
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['global_contourf_wrap'])
 def test_global_contourf_wrap_no_transform():
     ax = plt.axes(projection=ccrs.PlateCarree())
@@ -82,6 +87,7 @@ global_pcolor_wrap = ('global_pcolor_wrap'
                       'global_pcolor_wrap_pre_mpl_1.4.3')
 
 
+@pytest.mark.natural_earth
 @ImageTesting([global_pcolor_wrap])
 def test_global_pcolor_wrap_new_transform():
     ax = plt.axes(projection=ccrs.PlateCarree())
@@ -91,6 +97,7 @@ def test_global_pcolor_wrap_new_transform():
     plt.pcolor(x, y, data, transform=ccrs.PlateCarree())
 
 
+@pytest.mark.natural_earth
 @ImageTesting([global_pcolor_wrap])
 def test_global_pcolor_wrap_no_transform():
     ax = plt.axes(projection=ccrs.PlateCarree())
@@ -100,6 +107,7 @@ def test_global_pcolor_wrap_no_transform():
     plt.pcolor(x, y, data)
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['global_scatter_wrap'])
 def test_global_scatter_wrap_new_transform():
     ax = plt.axes(projection=ccrs.PlateCarree())
@@ -112,6 +120,7 @@ def test_global_scatter_wrap_new_transform():
     plt.scatter(x, y, c=data, transform=ccrs.PlateCarree())
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['global_scatter_wrap'])
 def test_global_scatter_wrap_no_transform():
     ax = plt.axes(projection=ccrs.PlateCarree())
@@ -137,6 +146,7 @@ def test_global_map():
              transform=ccrs.Geodetic())
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['simple_global'])
 def test_simple_global():
     plt.axes(projection=ccrs.PlateCarree())
@@ -144,6 +154,7 @@ def test_simple_global():
     # produces a global map, despite not having needed to set the limits
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['multiple_projections1'])
 def test_multiple_projections():
 
@@ -206,6 +217,7 @@ def test_cursor_values():
     plt.close()
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['natural_earth_interface'], tolerance=_ROB_TOL)
 def test_axes_natural_earth_interface():
     rob = ccrs.Robinson()
@@ -226,6 +238,7 @@ def test_axes_natural_earth_interface():
         assert 'add_feature' in msg
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['pcolormesh_global_wrap1'])
 def test_pcolormesh_global_with_wrap1():
     # make up some realistic data with bounds (such as data from the UM)
@@ -248,6 +261,7 @@ def test_pcolormesh_global_with_wrap1():
     ax.set_global()  # make sure everything is visible
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['pcolormesh_global_wrap2'])
 def test_pcolormesh_global_with_wrap2():
     # make up some realistic data with bounds (such as data from the UM)
@@ -274,6 +288,7 @@ def test_pcolormesh_global_with_wrap2():
     ax.set_global()  # make sure everything is visible
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['pcolormesh_global_wrap3'], tolerance=_ROB_TOL)
 def test_pcolormesh_global_with_wrap3():
     nx, ny = 33, 17
@@ -311,6 +326,7 @@ def test_pcolormesh_global_with_wrap3():
     ax.set_global()  # make sure everything is visible
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['pcolormesh_limited_area_wrap'],
               tolerance=1.41 if MPL_VERSION >= '2.1.0' else 0.7)
 def test_pcolormesh_limited_area_wrap():
@@ -349,6 +365,7 @@ def test_pcolormesh_limited_area_wrap():
     ax.coastlines()
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['pcolormesh_single_column_wrap'], tolerance=0.7)
 def test_pcolormesh_single_column_wrap():
     # Check a wrapped mesh like test_pcolormesh_limited_area_wrap, but only use
@@ -371,6 +388,7 @@ def test_pcolormesh_single_column_wrap():
     ax.set_global()
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['pcolormesh_goode_wrap'])
 def test_pcolormesh_goode_wrap():
     # global data on an Interrupted Goode Homolosine projection
@@ -385,6 +403,7 @@ def test_pcolormesh_goode_wrap():
     ax.pcolormesh(x, y, Z, transform=ccrs.PlateCarree())
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['pcolormesh_mercator_wrap'])
 def test_pcolormesh_mercator_wrap():
     x = np.linspace(0, 360, 73)
@@ -397,6 +416,7 @@ def test_pcolormesh_mercator_wrap():
     ax.pcolormesh(x, y, Z, transform=ccrs.PlateCarree())
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['quiver_plate_carree'])
 def test_quiver_plate_carree():
     x = np.arange(-60, 42.5, 2.5)
@@ -419,6 +439,7 @@ def test_quiver_plate_carree():
     ax.quiver(x, y, u, v, mag, transform=ccrs.PlateCarree())
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['quiver_rotated_pole'])
 def test_quiver_rotated_pole():
     nx, ny = 22, 36
@@ -443,6 +464,7 @@ def test_quiver_rotated_pole():
     ax.quiver(x, y, u, v, mag, transform=rp)
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['quiver_regrid'])
 def test_quiver_regrid():
     x = np.arange(-60, 42.5, 2.5)
@@ -460,6 +482,7 @@ def test_quiver_regrid():
               regrid_shape=30)
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['quiver_regrid_with_extent'])
 def test_quiver_regrid_with_extent():
     x = np.arange(-60, 42.5, 2.5)
@@ -478,6 +501,7 @@ def test_quiver_regrid_with_extent():
               regrid_shape=10, target_extent=target_extent)
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['barbs_plate_carree'])
 def test_barbs():
     x = np.arange(-60, 45, 5)
@@ -499,6 +523,7 @@ def test_barbs():
     ax.barbs(x, y, u, v, transform=ccrs.PlateCarree(), length=4, linewidth=.25)
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['barbs_regrid'])
 def test_barbs_regrid():
     x = np.arange(-60, 42.5, 2.5)
@@ -516,6 +541,7 @@ def test_barbs_regrid():
              length=4, linewidth=.4, regrid_shape=20)
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['barbs_regrid_with_extent'])
 def test_barbs_regrid_with_extent():
     x = np.arange(-60, 42.5, 2.5)
@@ -535,6 +561,7 @@ def test_barbs_regrid_with_extent():
              target_extent=target_extent)
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['barbs_1d'])
 def test_barbs_1d():
     x = np.array([20., 30., -17., 15.])
@@ -550,6 +577,7 @@ def test_barbs_1d():
              length=8, linewidth=1, color='#7f7f7f')
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['barbs_1d_transformed'])
 def test_barbs_1d_transformed():
     x = np.array([20., 30., -17., 15.])
@@ -565,6 +593,7 @@ def test_barbs_1d_transformed():
              length=8, linewidth=1, color='#7f7f7f')
 
 
+@pytest.mark.natural_earth
 @ImageTesting([_STREAMPLOT_IMAGE])
 def test_streamplot():
     x = np.arange(-60, 42.5, 2.5)
diff --git a/lib/cartopy/tests/mpl/test_shapely_to_mpl.py b/lib/cartopy/tests/mpl/test_shapely_to_mpl.py
index f9146ae..f46dde3 100644
--- a/lib/cartopy/tests/mpl/test_shapely_to_mpl.py
+++ b/lib/cartopy/tests/mpl/test_shapely_to_mpl.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2011 - 2017, Met Office
+# (C) British Crown Copyright 2011 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -22,6 +22,7 @@ import matplotlib.pyplot as plt
 import matplotlib.patches as mpatches
 from matplotlib.collections import PatchCollection
 from matplotlib.path import Path
+import pytest
 import shapely.geometry as sgeom
 
 import cartopy.crs as ccrs
@@ -30,6 +31,7 @@ import cartopy.mpl.patch as cpatch
 from cartopy.tests.mpl import MPL_VERSION, ImageTesting
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['poly_interiors'
                if MPL_VERSION >= '1.5' else
                'poly_interiors_pre_mpl_1.5'])
@@ -83,6 +85,7 @@ def test_polygon_interiors():
     ax.add_collection(collection)
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['contour_with_interiors'])
 def test_contour_interiors():
     # produces a polygon with multiple holes:
diff --git a/lib/cartopy/tests/mpl/test_ticks.py b/lib/cartopy/tests/mpl/test_ticks.py
index 3ec6844..1397f19 100644
--- a/lib/cartopy/tests/mpl/test_ticks.py
+++ b/lib/cartopy/tests/mpl/test_ticks.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2011 - 2017, Met Office
+# (C) British Crown Copyright 2011 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -61,6 +61,7 @@ else:
     ticks_tolerance = 0.5
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['xticks_no_transform' + test_fn_suffix],
               tolerance=ticks_tolerance)
 def test_set_xticks_no_transform():
@@ -72,6 +73,7 @@ def test_set_xticks_no_transform():
     ax.set_xticks([-135, -45, 45, 135], minor=True)
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['xticks_cylindrical' + test_fn_suffix],
               tolerance=ticks_tolerance)
 def test_set_xticks_cylindrical():
@@ -95,6 +97,7 @@ def test_set_xticks_non_cylindrical():
     plt.close()
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['yticks_no_transform' + test_fn_suffix],
               tolerance=ticks_tolerance)
 def test_set_yticks_no_transform():
@@ -106,6 +109,7 @@ def test_set_yticks_no_transform():
     ax.set_yticks([-75, -45, 15, 45, 75], minor=True)
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['yticks_cylindrical' + test_fn_suffix],
               tolerance=ticks_tolerance)
 def test_set_yticks_cylindrical():
@@ -129,6 +133,7 @@ def test_set_yticks_non_cylindrical():
     plt.close()
 
 
+@pytest.mark.natural_earth
 @ImageTesting(['xyticks' + test_fn_suffix], tolerance=ticks_tolerance)
 def test_set_xyticks():
     fig = plt.figure(figsize=(10, 10))
diff --git a/lib/cartopy/tests/mpl/test_web_services.py b/lib/cartopy/tests/mpl/test_web_services.py
index 1f50868..07aae88 100644
--- a/lib/cartopy/tests/mpl/test_web_services.py
+++ b/lib/cartopy/tests/mpl/test_web_services.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2014 - 2017, Met Office
+# (C) British Crown Copyright 2014 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -25,6 +25,7 @@ import cartopy.crs as ccrs
 from cartopy.io.ogc_clients import _OWSLIB_AVAILABLE
 
 
+@pytest.mark.network
 @pytest.mark.skipif(not _OWSLIB_AVAILABLE, reason='OWSLib is unavailable.')
 @ImageTesting(['wmts'], tolerance=7.56 if MPL_VERSION < '2' else 0)
 def test_wmts():
@@ -34,6 +35,7 @@ def test_wmts():
     ax.add_wmts(url, 'MODIS_Water_Mask')
 
 
+@pytest.mark.network
 @pytest.mark.skipif(not _OWSLIB_AVAILABLE, reason='OWSLib is unavailable.')
 @ImageTesting(['wms'], tolerance=7.76 if MPL_VERSION < '2' else 0)
 def test_wms():
diff --git a/lib/cartopy/tests/test_coastline.py b/lib/cartopy/tests/test_coastline.py
index 7c995cb..d1eb76d 100644
--- a/lib/cartopy/tests/test_coastline.py
+++ b/lib/cartopy/tests/test_coastline.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2011 - 2017, Met Office
+# (C) British Crown Copyright 2011 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -17,14 +17,17 @@
 
 from __future__ import (absolute_import, division, print_function)
 
+import pytest
+
 import cartopy
 import cartopy.io.shapereader as shp
 
-COASTLINE_PATH = shp.natural_earth()
-
 
+@pytest.mark.natural_earth
 class TestCoastline(object):
     def test_robust(self):
+        COASTLINE_PATH = shp.natural_earth()
+
         # Make sure all the coastlines can be projected without raising any
         # exceptions.
         projection = cartopy.crs.TransverseMercator(central_longitude=-90)
diff --git a/lib/cartopy/tests/test_coding_standards.py b/lib/cartopy/tests/test_coding_standards.py
index 0b0f1de..cfa1cd8 100644
--- a/lib/cartopy/tests/test_coding_standards.py
+++ b/lib/cartopy/tests/test_coding_standards.py
@@ -24,6 +24,8 @@ import os
 import re
 import subprocess
 
+import pytest
+
 import cartopy
 
 
@@ -133,8 +135,8 @@ class TestLicenseHeaders(object):
             last_change_by_fname = self.last_change_by_fname()
         except ValueError as e:
             # Caught the case where this is not a git repo.
-            return self.skipTest('cartopy installation did not look like a '
-                                 'git repo: ' + str(e))
+            return pytest.skip('cartopy installation did not look like a git '
+                               'repo: ' + str(e))
 
         failed = False
         for fname, last_change in sorted(last_change_by_fname.items()):
diff --git a/lib/cartopy/tests/test_crs.py b/lib/cartopy/tests/test_crs.py
index a9dc3ff..b54463c 100644
--- a/lib/cartopy/tests/test_crs.py
+++ b/lib/cartopy/tests/test_crs.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2011 - 2017, Met Office
+# (C) British Crown Copyright 2011 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -85,6 +85,7 @@ class TestCRS(object):
     def test_osgb(self):
         self._check_osgb(ccrs.OSGB())
 
+    @pytest.mark.network
     @pytest.mark.skipif(pyepsg is None, reason='requires pyepsg')
     def test_epsg(self):
         uk = ccrs.epsg(27700)
diff --git a/lib/cartopy/tests/test_img_nest.py b/lib/cartopy/tests/test_img_nest.py
index aac87dd..97ee401 100644
--- a/lib/cartopy/tests/test_img_nest.py
+++ b/lib/cartopy/tests/test_img_nest.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2011 - 2017, Met Office
+# (C) British Crown Copyright 2011 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -219,6 +219,7 @@ class RoundedImg(cimg_nest.Img):
 
 
 @pytest.mark.xfail(reason='MapQuest is unavailable')
+@pytest.mark.network
 def test_nest(nest_from_config):
     crs = cimgt.GoogleTiles().crs
     z0 = cimg_nest.ImageCollection('aerial z0 test', crs)
@@ -365,6 +366,7 @@ def wmts_data():
 
 
 @pytest.mark.xfail(reason='MapQuest is unavailable')
+@pytest.mark.network
 def test_find_images(wmts_data):
     z2_dir = os.path.join(_TEST_DATA_DIR, 'z_2')
     img_fname = os.path.join(z2_dir, 'x_2_y_0.png')
diff --git a/lib/cartopy/tests/test_img_tiles.py b/lib/cartopy/tests/test_img_tiles.py
index 11efda5..9af3cc9 100644
--- a/lib/cartopy/tests/test_img_tiles.py
+++ b/lib/cartopy/tests/test_img_tiles.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2011 - 2017, Met Office
+# (C) British Crown Copyright 2011 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -141,6 +141,7 @@ def test_tile_find_images():
             [(7, 4, 4), (7, 5, 4), (8, 4, 4), (8, 5, 4)])
 
 
+@pytest.mark.network
 def test_image_for_domain():
     gt = cimgt.GoogleTiles()
     gt._image_url = types.MethodType(GOOGLE_IMAGE_URL_REPLACEMENT, gt)
diff --git a/lib/cartopy/tests/test_shapereader.py b/lib/cartopy/tests/test_shapereader.py
index 0e17fac..895721a 100644
--- a/lib/cartopy/tests/test_shapereader.py
+++ b/lib/cartopy/tests/test_shapereader.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2011 - 2017, Met Office
+# (C) British Crown Copyright 2011 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -19,20 +19,17 @@ from __future__ import (absolute_import, division, print_function)
 
 import numpy as np
 from numpy.testing import assert_array_almost_equal
+import pytest
 
 import cartopy.io.shapereader as shp
 
 
-LAKES_PATH = shp.natural_earth(resolution='110m',
-                               category='physical',
-                               name='lakes')
-RIVERS_PATH = shp.natural_earth(resolution='110m',
-                                category='physical',
-                                name='rivers_lake_centerlines')
-
-
+@pytest.mark.natural_earth
 class TestLakes(object):
     def setup_class(self):
+        LAKES_PATH = shp.natural_earth(resolution='110m',
+                                       category='physical',
+                                       name='lakes')
         self.reader = shp.Reader(LAKES_PATH)
         names = [record.attributes['name'] for record in self.reader.records()]
         # Choose a nice small lake
@@ -83,8 +80,12 @@ class TestLakes(object):
             'The geometry was loaded in order to create the bounds.'
 
 
+@pytest.mark.natural_earth
 class TestRivers(object):
     def setup_class(self):
+        RIVERS_PATH = shp.natural_earth(resolution='110m',
+                                        category='physical',
+                                        name='rivers_lake_centerlines')
         self.reader = shp.Reader(RIVERS_PATH)
         names = [record.attributes['name'] for record in self.reader.records()]
         # Choose a nice small river
diff --git a/lib/cartopy/util.py b/lib/cartopy/util.py
index ec181ba..0725e8d 100644
--- a/lib/cartopy/util.py
+++ b/lib/cartopy/util.py
@@ -56,26 +56,33 @@ def add_cyclic_point(data, coord=None, axis=-1):
     Adding a cyclic point to a data array, where the cyclic dimension is
     the right-most dimension
 
+    .. testsetup::
+        >>> from distutils.version import LooseVersion
+        >>> import numpy as np
+        >>> if LooseVersion(np.__version__) >= '1.14.0':
+        ...     # To provide consistent doctests.
+        ...     np.set_printoptions(legacy='1.13')
+
     >>> import numpy as np
     >>> data = np.ones([5, 6]) * np.arange(6)
     >>> cyclic_data = add_cyclic_point(data)
-    >>> print(cyclic_data)
-    [[0. 1. 2. 3. 4. 5. 0.]
-     [0. 1. 2. 3. 4. 5. 0.]
-     [0. 1. 2. 3. 4. 5. 0.]
-     [0. 1. 2. 3. 4. 5. 0.]
-     [0. 1. 2. 3. 4. 5. 0.]]
+    >>> print(cyclic_data)  # doctest: +NORMALIZE_WHITESPACE
+    [[ 0. 1. 2. 3. 4. 5. 0.]
+     [ 0. 1. 2. 3. 4. 5. 0.]
+     [ 0. 1. 2. 3. 4. 5. 0.]
+     [ 0. 1. 2. 3. 4. 5. 0.]
+     [ 0. 1. 2. 3. 4. 5. 0.]]
 
     Adding a cyclic point to a data array and an associated coordinate
 
     >>> lons = np.arange(0, 360, 60)
     >>> cyclic_data, cyclic_lons = add_cyclic_point(data, coord=lons)
-    >>> print(cyclic_data)
-    [[0. 1. 2. 3. 4. 5. 0.]
-     [0. 1. 2. 3. 4. 5. 0.]
-     [0. 1. 2. 3. 4. 5. 0.]
-     [0. 1. 2. 3. 4. 5. 0.]
-     [0. 1. 2. 3. 4. 5. 0.]]
+    >>> print(cyclic_data)  # doctest: +NORMALIZE_WHITESPACE
+    [[ 0. 1. 2. 3. 4. 5. 0.]
+     [ 0. 1. 2. 3. 4. 5. 0.]
+     [ 0. 1. 2. 3. 4. 5. 0.]
+     [ 0. 1. 2. 3. 4. 5. 0.]
+     [ 0. 1. 2. 3. 4. 5. 0.]]
     >>> print(cyclic_lons)
     [  0  60 120 180 240 300 360]
 
-- 
2.14.3