diff -Nru mapproxy-1.11.0/.appveyor.yml mapproxy-1.12.0/.appveyor.yml --- mapproxy-1.11.0/.appveyor.yml 1970-01-01 00:00:00.000000000 +0000 +++ mapproxy-1.12.0/.appveyor.yml 2019-08-30 07:34:08.000000000 +0000 @@ -0,0 +1,128 @@ +environment: + matrix: + + + # Pre-installed Python versions, which Appveyor may upgrade to + # a later point release. + # See: http://www.appveyor.com/docs/installed-software#python + + - PYTHON_VERSION: 2.7 + MINICONDA: C:\Miniconda + PYTHON_ARCH: "32" + + # - PYTHON_VERSION: 3.4 + # MINICONDA: C:\Miniconda3 + # PYTHON_ARCH: "32" + + # - PYTHON_VERSION: 3.5 + # MINICONDA: C:\Miniconda35 + # PYTHON_ARCH: "32" + + # - PYTHON_VERSION: 3.6 + # MINICONDA: C:\Miniconda36 + # PYTHON_ARCH: "32" + + # - PYTHON_VERSION: 2.7 + # MINICONDA: C:\Miniconda-x64 + # PYTHON_ARCH: "64" + + # - PYTHON_VERSION: 3.4 + # MINICONDA: C:\Miniconda3-x64 + # PYTHON_ARCH: "64" + + # - PYTHON_VERSION: 3.5 + # MINICONDA: C:\Miniconda35-x64 + # PYTHON_ARCH: "64" + + - PYTHON_VERSION: 3.6 + MINICONDA: C:\Miniconda36-x64 + PYTHON_ARCH: "64" + + + # - PYTHON: "C:\\Python27" + # PYTHON_VERSION: "2.7.x" + # PYTHON_ARCH: "32" + + + # - PYTHON: "C:\\Python27-x64" + # PYTHON_VERSION: "2.7.x" + # PYTHON_ARCH: "64" + + # - PYTHON: "C:\\Python34" + # PYTHON_VERSION: "3.4.x" + # PYTHON_ARCH: "32" + + # - PYTHON: "C:\\Python34-x64" + # PYTHON_VERSION: "3.4.x" + # PYTHON_ARCH: "64" + + # - PYTHON: "C:\\Python35" + # PYTHON_VERSION: "3.5.x" + # PYTHON_ARCH: "32" + + # - PYTHON: "C:\\Python35-x64" + # PYTHON_VERSION: "3.5.x" + # PYTHON_ARCH: "64" + + # - PYTHON: "C:\\Python36" + # PYTHON_VERSION: "3.6.x" + # PYTHON_ARCH: "32" + + # - PYTHON: "C:\\Python36-x64" + # PYTHON_VERSION: "3.6.x" + # PYTHON_ARCH: "64" + +install: + + - "set PATH=%MINICONDA%;%MINICONDA%\\Scripts;%PATH%" + - conda config --set always_yes yes --set changeps1 no + - conda update -q conda + - conda info -a + - conda config --add channels conda-forge + + # If there is a newer build queued for the same PR, cancel this one. + # The AppVeyor 'rollout builds' option is supposed to serve the same + # purpose but it is problematic because it tends to cancel builds pushed + # directly to master instead of just PR builds (or the converse). + # credits: JuliaLang developers. + - ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod ` + https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | ` + Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { ` + throw "There are newer queued builds for this pull request, failing early." } + - ECHO "Filesystem root:" + - ps: "ls \"C:/\"" + + - ECHO "Installed SDKs:" + - ps: "ls \"C:/Program Files/Microsoft SDKs/Windows\"" + + # Check that we have the expected version and architecture for Python + - "python --version" + - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" + + - "conda create -q -n test-environment python=%PYTHON_VERSION%" + - activate test-environment + - conda install Shapely Pillow pyproj lxml fiona pyyaml + - pip install -r requirements-appveyor.txt" + +build_script: + # Build the compiled extension + - python setup.py build" + +test_script: + # Run the project tests + - pytest mapproxy" + +after_test: + # If tests are successful, create binary packages for the project. + - python setup.py bdist_wheel" + - python setup.py bdist_wininst" + - python setup.py bdist_msi" + - ps: "ls dist" + +artifacts: + # Archive the generated packages in the ci.appveyor.com build report. + - path: dist\* + +#on_success: +# - TODO: upload the content of dist/*.whl to a public wheelhouse +# diff -Nru mapproxy-1.11.0/CHANGES.txt mapproxy-1.12.0/CHANGES.txt --- mapproxy-1.11.0/CHANGES.txt 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/CHANGES.txt 2019-08-30 07:34:08.000000000 +0000 @@ -1,4 +1,47 @@ -1.11.0 2017-11-xx +1.12.0 2019-08-30 +~~~~~~~~~~~~~~~~~ + +Improvements: + +- Cache: Rescale tiles existing tiles with new upscale_tiles/downscale_tiles option. +- Cache: Finer control for reprojection sources with preferred_src_proj option. +- WMS: Add georeference to TIFF images (GeoTIFF). +- WMS: Support for compressed TIFF images (LZW/JPEG). +- WMS: Advertise MaxWidth/MaxHeight. +- WMTS: Support for FeatureInfo requests. +- WMS/ArcGIS: Add on_error handler (similar to on_error handling for tile sources). +- WMS/WMTS: Add keyword_list to capabilities. +- S3: Support for custom S3 server and ACL. +- autoconfig: Support for username/password in URL. + +Fixes: + +- Various fixes for Python 3.6 and 3.7 compatibility. +- Set explicit permissions for write_atomic on Windows. + + +Other: + +- WMS: Respond with highest supported WMS version (1.3.0 by default). +- Test: Now uses pytest instead of nosetest for all unit and system tests. +- YAML: Always use load_safe to support PyYAML >3 without warnings. +- Paster and eventlet specific code removed. + + +1.11.1 2019-08-06 +~~~~~~~~~~~~~~~~~ + +Fixes: + +- Fix Cross Site Scripting (XSS) issue in demo service. Fix for #322 did not + properly escaped input used in JavaScript examples. Found by Janek Vind. + + A targeted attack could be used for information disclosure. For + example: Session cookies of a third party application running on + the same domain. + + +1.11.0 2017-11-20 ~~~~~~~~~~~~~~~~~ Improvements: diff -Nru mapproxy-1.11.0/debian/changelog mapproxy-1.12.0/debian/changelog --- mapproxy-1.11.0/debian/changelog 2017-11-20 16:07:46.000000000 +0000 +++ mapproxy-1.12.0/debian/changelog 2020-03-08 11:00:00.000000000 +0000 @@ -1,3 +1,76 @@ +mapproxy (1.12.0-1~bionic1) bionic; urgency=medium + + * No change rebuild for GDAL 3.0.4 transition. + + -- Angelos Tzotsos Sun, 08 Mar 2020 13:00:00 +0200 + +mapproxy (1.12.0-1~bionic0) bionic; urgency=medium + + * No change rebuild for Bionic. + + -- Angelos Tzotsos Mon, 25 Nov 2019 15:00:00 +0200 + +mapproxy (1.12.0-1) unstable; urgency=medium + + * Move from experimental to unstable. + + -- Bas Couwenberg Mon, 02 Sep 2019 06:44:53 +0200 + +mapproxy (1.12.0-1~exp1) experimental; urgency=medium + + * New upstream release. + * Update build dependencies for pytest instead of nose. + * Drop patches applied upstream, refresh remaining patch. + * Remove obsolete environment variable for tests. + + -- Bas Couwenberg Fri, 30 Aug 2019 13:24:50 +0200 + +mapproxy (1.11.1-2) unstable; urgency=medium + + * Add upstream patch to fix WMS Capabilties with Python 3.7. + (closes: #935887) + + -- Bas Couwenberg Tue, 27 Aug 2019 18:47:19 +0200 + +mapproxy (1.11.1-1) unstable; urgency=high + + * New upstream release. + Fixes XSS issue in demo service, see: + https://github.com/mapproxy/mapproxy/issues/322 + + -- Bas Couwenberg Tue, 06 Aug 2019 13:00:23 +0200 + +mapproxy (1.11.0-4) unstable; urgency=medium + + * Bump Standards-Version to 4.4.0, no changes. + * Update watch file to limit matches to archive path. + * Drop unused override for python-module-in-wrong-location. + * Update gbp.conf to use --source-only-changes by default. + * Drop Python 2 support. + + -- Bas Couwenberg Sun, 21 Jul 2019 09:52:17 +0200 + +mapproxy (1.11.0-3) unstable; urgency=medium + + * Drop autopkgtest to test installability. + * Add lintian override for testsuite-autopkgtest-missing. + * Add lintian override for python-module-in-wrong-location. + + -- Bas Couwenberg Tue, 31 Jul 2018 21:42:22 +0200 + +mapproxy (1.11.0-2) unstable; urgency=medium + + * Update Vcs-* URLs for Salsa. + * Bump Standards-Version to 4.1.5, no changes. + * Drop ancient X-Python-Version field. + * Strip trailing whitespace from control & rules files. + * Add patch to rename async.py to async_.py for Python 3.7 compatibility. + * Add lintian overrides for embedded JS & fonts. + * Remove documentation outside usr/share/doc. + * Fix 'every time' typo. + + -- Bas Couwenberg Fri, 20 Jul 2018 19:11:25 +0200 + mapproxy (1.11.0-1) unstable; urgency=medium * New upstream release. diff -Nru mapproxy-1.11.0/debian/control mapproxy-1.12.0/debian/control --- mapproxy-1.11.0/debian/control 2017-11-20 15:50:01.000000000 +0000 +++ mapproxy-1.12.0/debian/control 2019-09-01 11:07:44.000000000 +0000 @@ -6,24 +6,11 @@ Build-Depends: debhelper (>= 9), dh-python, libgdal-dev, - python-all, - python-lxml, - python-nose, - python-pil, - python-pkg-resources, - python-redis, - python-requests, - python-setuptools, - python-shapely, - python-sphinx, - python-sphinx-bootstrap-theme, - python-webtest, - python-yaml, python3-all, python3-lxml, - python3-nose, python3-pil, python3-pkg-resources, + python3-pytest, python3-redis, python3-requests, python3-setuptools, @@ -36,20 +23,17 @@ docbook-xsl, docbook-xml, xsltproc -Standards-Version: 4.1.1 -Vcs-Browser: https://anonscm.debian.org/cgit/pkg-grass/mapproxy.git -Vcs-Git: https://anonscm.debian.org/git/pkg-grass/mapproxy.git +Standards-Version: 4.4.0 +Vcs-Browser: https://salsa.debian.org/debian-gis-team/mapproxy +Vcs-Git: https://salsa.debian.org/debian-gis-team/mapproxy.git Homepage: http://mapproxy.org/ -X-Python-Version: >= 2.7 Package: mapproxy Architecture: all Section: web -Depends: python-mapproxy (= ${binary:Version}), +Depends: python3-mapproxy (= ${binary:Version}), ${misc:Depends} Suggests: mapproxy-doc -Breaks: python-mapproxy (<< 1.8.2-1~) -Replaces: python-mapproxy (<< 1.8.2-1~) Description: open source proxy for geospatial data MapProxy is an open source proxy for geospatial data. It caches, accelerates and transforms data from existing map services and serves any desktop or web @@ -60,32 +44,6 @@ . This package provides the mapproxy utilities. -Package: python-mapproxy -Architecture: all -Depends: fonts-dejavu-core, - python-pil, - python-pkg-resources, - python-pyproj, - python-yaml, - ${python:Depends}, - ${misc:Depends} -Recommends: python-boto3, - python-botocore, - python-gdal, - python-lxml, - python-pastedeploy, - python-redis, - python-shapely -Description: open source proxy for geospatial data - Python 2 module - MapProxy is an open source proxy for geospatial data. It caches, accelerates - and transforms data from existing map services and serves any desktop or web - GIS client. - . - MapProxy is a tile server (WMS-C, TMS, WMTS, KML SuperOverlays), and also a - fully compliant WMS server supporting any WMS client (desktop and web). - . - This package provides the mapproxy module for Python 2. - Package: python3-mapproxy Architecture: all Depends: fonts-dejavu-core, @@ -126,4 +84,3 @@ fully compliant WMS server supporting any WMS client (desktop and web). . This package provides the MapProxy documentation. - diff -Nru mapproxy-1.11.0/debian/gbp.conf mapproxy-1.12.0/debian/gbp.conf --- mapproxy-1.11.0/debian/gbp.conf 2017-08-23 06:14:27.000000000 +0000 +++ mapproxy-1.12.0/debian/gbp.conf 2019-09-01 11:07:44.000000000 +0000 @@ -14,3 +14,6 @@ # Always use pristine-tar. pristine-tar = True + +[buildpackage] +pbuilder-options = --source-only-changes diff -Nru mapproxy-1.11.0/debian/man/mapproxy-util-autoconfig.1.xml mapproxy-1.12.0/debian/man/mapproxy-util-autoconfig.1.xml --- mapproxy-1.11.0/debian/man/mapproxy-util-autoconfig.1.xml 2016-08-22 12:38:54.000000000 +0000 +++ mapproxy-1.12.0/debian/man/mapproxy-util-autoconfig.1.xml 2018-07-20 17:55:29.000000000 +0000 @@ -164,7 +164,7 @@ define another coverage, disable featureinfo, etc. You can do this by editing the output file of course, or you can modify the output by defining all changes to an overwrite file. - Overwrite files are applied everytime you call + Overwrite files are applied every time you call mapproxy-util autoconfig. diff -Nru mapproxy-1.11.0/debian/mapproxy-doc.lintian-overrides mapproxy-1.12.0/debian/mapproxy-doc.lintian-overrides --- mapproxy-1.11.0/debian/mapproxy-doc.lintian-overrides 1970-01-01 00:00:00.000000000 +0000 +++ mapproxy-1.12.0/debian/mapproxy-doc.lintian-overrides 2018-07-20 17:53:56.000000000 +0000 @@ -0,0 +1,8 @@ +# libjs-twitter-bootstrap is not compatible +embedded-javascript-library usr/share/doc/mapproxy/html/_static/bootstrap-*/js/bootstrap.js please use libjs-twitter-bootstrap +font-in-non-font-package usr/share/doc/mapproxy/html/_static/boot*/fonts/* +font-outside-font-dir usr/share/doc/mapproxy/html/_static/boots*/fonts/* + +# libjs-jquery is not compatible +embedded-javascript-library usr/share/doc/mapproxy/html/_static/js/jquery* please use libjs-jquery + diff -Nru mapproxy-1.11.0/debian/mapproxy.links mapproxy-1.12.0/debian/mapproxy.links --- mapproxy-1.11.0/debian/mapproxy.links 2017-03-20 19:21:45.000000000 +0000 +++ mapproxy-1.12.0/debian/mapproxy.links 2019-09-01 11:07:44.000000000 +0000 @@ -1,2 +1,2 @@ -usr/lib/python-mapproxy/mapproxy-seed usr/bin/mapproxy-seed -usr/lib/python-mapproxy/mapproxy-util usr/bin/mapproxy-util +usr/lib/python3-mapproxy/mapproxy-seed usr/bin/mapproxy-seed +usr/lib/python3-mapproxy/mapproxy-util usr/bin/mapproxy-util diff -Nru mapproxy-1.11.0/debian/patches/disable-tag_date.patch mapproxy-1.12.0/debian/patches/disable-tag_date.patch --- mapproxy-1.11.0/debian/patches/disable-tag_date.patch 2017-05-18 17:32:09.000000000 +0000 +++ mapproxy-1.12.0/debian/patches/disable-tag_date.patch 2019-09-01 11:07:44.000000000 +0000 @@ -8,7 +8,7 @@ --- a/setup.cfg +++ b/setup.cfg -@@ -6,7 +6,7 @@ with-doctest = 1 +@@ -6,7 +6,7 @@ testpaths = mapproxy [egg_info] #tag_build = .dev @@ -16,6 +16,4 @@ +tag_date = false [bdist_wheel] --universal=1 -\ No newline at end of file -+universal=1 + universal=1 diff -Nru mapproxy-1.11.0/debian/patches/offline-tests.patch mapproxy-1.12.0/debian/patches/offline-tests.patch --- mapproxy-1.11.0/debian/patches/offline-tests.patch 2017-11-20 15:48:03.000000000 +0000 +++ mapproxy-1.12.0/debian/patches/offline-tests.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,369 +0,0 @@ -Description: Disable tests that require network. -Author: Bas Couwenberg -Forwarded: not-needed - ---- a/mapproxy/test/system/test_util_wms_capabilities.py -+++ b/mapproxy/test/system/test_util_wms_capabilities.py -@@ -16,6 +16,7 @@ - import os - - from nose.tools import assert_raises -+from nose.plugins.skip import SkipTest - - from mapproxy.client.http import HTTPClient - from mapproxy.script.wms_capabilities import wms_capabilities_command -@@ -35,6 +36,9 @@ class TestUtilWMSCapabilities(object): - self.args = ['command_dummy', '--host', TESTSERVER_URL + '/service'] - - def test_http_error(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - self.args = ['command_dummy', '--host', 'http://foo.doesnotexist'] - with capture() as (out,err): - assert_raises(SystemExit, wms_capabilities_command, self.args) -@@ -46,6 +50,9 @@ class TestUtilWMSCapabilities(object): - assert err.getvalue().startswith("ERROR:") - - def test_request_not_parsable(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - with mock_httpd(TESTSERVER_ADDRESS, [({'path': '/service?request=GetCapabilities&version=1.1.1&service=WMS', 'method': 'GET'}, - {'status': '200', 'body': ''})]): - with capture() as (out,err): -@@ -54,6 +61,9 @@ class TestUtilWMSCapabilities(object): - assert error_msg.startswith('Could not parse the document') - - def test_service_exception(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - self.args = ['command_dummy', '--host', TESTSERVER_URL + '/service?request=GetCapabilities'] - with open(SERVICE_EXCEPTION_FILE, 'rb') as fp: - capabilities_doc = fp.read() -@@ -65,6 +75,9 @@ class TestUtilWMSCapabilities(object): - assert 'Not a capabilities document' in error_msg - - def test_parse_capabilities(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - self.args = ['command_dummy', '--host', TESTSERVER_URL + '/service?request=GetCapabilities', '--version', '1.1.1'] - with open(CAPABILITIES111_FILE, 'rb') as fp: - capabilities_doc = fp.read() -@@ -76,6 +89,9 @@ class TestUtilWMSCapabilities(object): - assert lines[1].startswith('Capabilities Document Version 1.1.1') - - def test_parse_130capabilities(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - self.args = ['command_dummy', '--host', TESTSERVER_URL + '/service?request=GetCapabilities', '--version', '1.3.0'] - with open(CAPABILITIES130_FILE, 'rb') as fp: - capabilities_doc = fp.read() -@@ -87,6 +103,9 @@ class TestUtilWMSCapabilities(object): - assert lines[1].startswith('Capabilities Document Version 1.3.0') - - def test_key_error(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - self.args = ['command_dummy', '--host', TESTSERVER_URL + '/service?request=GetCapabilities'] - with open(CAPABILITIES111_FILE, 'rb') as fp: - capabilities_doc = fp.read() ---- a/mapproxy/test/unit/test_cache.py -+++ b/mapproxy/test/unit/test_cache.py -@@ -55,6 +55,7 @@ from mapproxy.test.http import assert_qu - from collections import defaultdict - - from nose.tools import eq_, raises, assert_not_equal, assert_raises -+from nose.plugins.skip import SkipTest - - TEST_SERVER_ADDRESS = ('127.0.0.1', 56413) - GLOBAL_GEOGRAPHIC_EXTENT = MapExtent((-180, -90, 180, 90), SRS(4326)) -@@ -743,6 +744,9 @@ class TestWMSSourceWithClient(object): - self.source = WMSSource(self.client) - - def test_get_map(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - with tmp_image((512, 512)) as img: - expected_req = ({'path': r'/service?LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=512&SRS=EPSG%3A4326&styles=' -@@ -756,6 +760,9 @@ class TestWMSSourceWithClient(object): - assert is_png(result.as_buffer(seekable=True)) - eq_(result.as_image().size, (512, 512)) - def test_get_map_non_image_content_type(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - with tmp_image((512, 512)) as img: - expected_req = ({'path': r'/service?LAYERS=foo&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=512&SRS=EPSG%3A4326&styles=' -@@ -770,6 +777,9 @@ class TestWMSSourceWithClient(object): - else: - assert False, 'no SourceError raised' - def test_basic_auth(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - http_client = HTTPClient(self.req_template.url, username='foo', password='bar@') - self.client.http_client = http_client - def assert_auth(req_handler): -@@ -935,4 +945,4 @@ class TestNeastedConditionalLayers(objec - assert self.l4326.requested - def test_resolution_low_projected(self): - self.layer.get_map(MapQuery((0, 0, 10000, 10000), (100, 100), SRS(31467))) -- assert self.l900913.requested -\ No newline at end of file -+ assert self.l900913.requested ---- a/mapproxy/test/unit/test_client.py -+++ b/mapproxy/test/unit/test_client.py -@@ -42,11 +42,17 @@ class TestHTTPClient(object): - self.client = HTTPClient() - - def test_post(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - with mock_httpd(TESTSERVER_ADDRESS, [({'path': '/service?foo=bar', 'method': 'POST'}, - {'status': '200', 'body': b''})]): - self.client.open(TESTSERVER_URL + '/service', data=b"foo=bar") - - def test_internal_error_response(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - try: - with mock_httpd(TESTSERVER_ADDRESS, [({'path': '/'}, - {'status': '500', 'body': b''})]): -@@ -56,6 +62,9 @@ class TestHTTPClient(object): - else: - assert False, 'expected HTTPClientError' - def test_invalid_url_type(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - try: - self.client.open('htp://example.org') - except HTTPClientError as e: -@@ -63,6 +72,9 @@ class TestHTTPClient(object): - else: - assert False, 'expected HTTPClientError' - def test_invalid_url(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - try: - self.client.open('this is not a url') - except HTTPClientError as e: -@@ -70,6 +82,9 @@ class TestHTTPClient(object): - else: - assert False, 'expected HTTPClientError' - def test_unknown_host(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - try: - self.client.open('http://thishostshouldnotexist000136really42.org') - except HTTPClientError as e: -@@ -77,6 +92,9 @@ class TestHTTPClient(object): - else: - assert False, 'expected HTTPClientError' - def test_no_connect(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - try: - self.client.open('http://localhost:53871') - except HTTPClientError as e: -@@ -86,6 +104,9 @@ class TestHTTPClient(object): - - @attr('online') - def test_https_untrusted_root(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - if not supports_ssl_default_context: - # old python versions require ssl_ca_certs - raise SkipTest() -@@ -97,12 +118,18 @@ class TestHTTPClient(object): - - @attr('online') - def test_https_insecure(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - self.client = HTTPClient( - 'https://untrusted-root.badssl.com/', insecure=True) - self.client.open('https://untrusted-root.badssl.com/') - - @attr('online') - def test_https_valid_ca_cert_file(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - # verify with fixed ca_certs file - cert_file = '/etc/ssl/certs/ca-certificates.crt' - if os.path.exists(cert_file): -@@ -117,6 +144,9 @@ class TestHTTPClient(object): - - @attr('online') - def test_https_valid_default_cert(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - # modern python should verify by default - if not supports_ssl_default_context: - raise SkipTest() -@@ -125,6 +155,9 @@ class TestHTTPClient(object): - - @attr('online') - def test_https_invalid_cert(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - # load 'wrong' root cert - with TempFile() as tmp: - with open(tmp, 'wb') as f: -@@ -137,6 +170,9 @@ class TestHTTPClient(object): - assert_re(e.args[0], r'Could not verify connection to URL') - - def test_timeouts(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - test_req = ({'path': '/', 'req_assert_function': lambda x: time.sleep(0.9) or True}, - {'body': b'nothing'}) - -@@ -240,6 +276,9 @@ class TestTMSClient(object): - def setup(self): - self.client = TMSClient(TESTSERVER_URL) - def test_get_tile(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - with mock_httpd(TESTSERVER_ADDRESS, [({'path': '/9/5/13.png'}, - {'body': b'tile', 'headers': {'content-type': 'image/png'}})]): - resp = self.client.get_tile((5, 13, 9)).source.read() -@@ -247,6 +286,9 @@ class TestTMSClient(object): - - class TestTileClient(object): - def test_tc_path(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - template = TileURLTemplate(TESTSERVER_URL + '/%(tc_path)s.png') - client = TileClient(template) - with mock_httpd(TESTSERVER_ADDRESS, [({'path': '/09/000/000/005/000/000/013.png'}, -@@ -256,6 +298,9 @@ class TestTileClient(object): - eq_(resp, b'tile') - - def test_quadkey(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - template = TileURLTemplate(TESTSERVER_URL + '/key=%(quadkey)s&format=%(format)s') - client = TileClient(template) - with mock_httpd(TESTSERVER_ADDRESS, [({'path': '/key=000002303&format=png'}, -@@ -264,6 +309,9 @@ class TestTileClient(object): - resp = client.get_tile((5, 13, 9)).source.read() - eq_(resp, b'tile') - def test_xyz(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - template = TileURLTemplate(TESTSERVER_URL + '/x=%(x)s&y=%(y)s&z=%(z)s&format=%(format)s') - client = TileClient(template) - with mock_httpd(TESTSERVER_ADDRESS, [({'path': '/x=5&y=13&z=9&format=png'}, -@@ -273,6 +321,9 @@ class TestTileClient(object): - eq_(resp, b'tile') - - def test_arcgiscache_path(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - template = TileURLTemplate(TESTSERVER_URL + '/%(arcgiscache_path)s.png') - client = TileClient(template) - with mock_httpd(TESTSERVER_ADDRESS, [({'path': '/L09/R0000000d/C00000005.png'}, -@@ -282,6 +333,9 @@ class TestTileClient(object): - eq_(resp, b'tile') - - def test_bbox(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - grid = tile_grid(4326) - template = TileURLTemplate(TESTSERVER_URL + '/service?BBOX=%(bbox)s') - client = TileClient(template, grid=grid) ---- a/mapproxy/test/unit/test_tiled_source.py -+++ b/mapproxy/test/unit/test_tiled_source.py -@@ -14,6 +14,8 @@ - # limitations under the License. - - -+import os -+ - from mapproxy.client.tile import TMSClient - from mapproxy.grid import TileGrid - from mapproxy.srs import SRS -@@ -23,6 +25,7 @@ from mapproxy.layer import MapQuery - - from mapproxy.test.http import mock_httpd - from nose.tools import eq_ -+from nose.plugins.skip import SkipTest - - TEST_SERVER_ADDRESS = ('127.0.0.1', 56413) - TESTSERVER_URL = 'http://%s:%d' % TEST_SERVER_ADDRESS -@@ -33,6 +36,9 @@ class TestTileClientOnError(object): - self.client = TMSClient(TESTSERVER_URL) - - def test_cacheable_response(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - error_handler = HTTPSourceErrorHandler() - error_handler.add_handler(500, (255, 0, 0), cacheable=True) - self.source = TiledSource(self.grid, self.client, error_handler=error_handler) -@@ -44,6 +50,9 @@ class TestTileClientOnError(object): - eq_(resp.as_image().getcolors(), [((256*256), (255, 0, 0))]) - - def test_image_response(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - error_handler = HTTPSourceErrorHandler() - error_handler.add_handler(500, (255, 0, 0), cacheable=False) - self.source = TiledSource(self.grid, self.client, error_handler=error_handler) -@@ -55,6 +64,9 @@ class TestTileClientOnError(object): - eq_(resp.as_image().getcolors(), [((256*256), (255, 0, 0))]) - - def test_multiple_image_responses(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - error_handler = HTTPSourceErrorHandler() - error_handler.add_handler(500, (255, 0, 0), cacheable=False) - error_handler.add_handler(204, (255, 0, 127, 200), cacheable=True) ---- a/mapproxy/test/system/test_seed.py -+++ b/mapproxy/test/system/test_seed.py -@@ -31,6 +31,7 @@ from mapproxy.test.http import mock_http - from mapproxy.test.image import tmp_image, create_tmp_image_buf, create_tmp_image - - from nose.tools import eq_ -+from nose.plugins.skip import SkipTest - - FIXTURE_DIR = os.path.join(os.path.dirname(__file__), 'fixture') - -@@ -356,6 +357,9 @@ class TestConcurrentRequestsSeed(SeedTes - empty_ogrdata = 'empty_ogrdata.geojson' - - def test_timeout(self): -+ if 'OFFLINE_TESTS' in os.environ: -+ raise SkipTest -+ - # test concurrent seeding where seed concurrency is higher than the permitted - # concurrent_request value of the source and a lock times out - diff -Nru mapproxy-1.11.0/debian/patches/series mapproxy-1.12.0/debian/patches/series --- mapproxy-1.11.0/debian/patches/series 2017-05-18 17:12:53.000000000 +0000 +++ mapproxy-1.12.0/debian/patches/series 2019-09-01 11:07:44.000000000 +0000 @@ -1,3 +1 @@ -offline-tests.patch disable-tag_date.patch -skip-tests-for-missing-files.patch diff -Nru mapproxy-1.11.0/debian/patches/skip-tests-for-missing-files.patch mapproxy-1.12.0/debian/patches/skip-tests-for-missing-files.patch --- mapproxy-1.11.0/debian/patches/skip-tests-for-missing-files.patch 2017-05-18 17:54:27.000000000 +0000 +++ mapproxy-1.12.0/debian/patches/skip-tests-for-missing-files.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,67 +0,0 @@ -Description: Skip tests for missing data files. -Author: Bas Couwenberg -Forwarded: not-needed - ---- a/mapproxy/test/system/test_cache_geopackage.py -+++ b/mapproxy/test/system/test_cache_geopackage.py -@@ -27,6 +27,7 @@ from mapproxy.test.system import prepare - from mapproxy.cache.geopackage import GeopackageCache - from mapproxy.grid import TileGrid - from nose.tools import eq_ -+from nose.plugins.skip import SkipTest - import sqlite3 - - test_config = {} -@@ -35,6 +36,9 @@ test_config = {} - def setup_module(): - prepare_env(test_config, 'cache_geopackage.yaml') - -+ if not os.path.exists(os.path.join(test_config['fixture_dir'], 'cache.gpkg')): -+ raise SkipTest -+ - shutil.copy(os.path.join(test_config['fixture_dir'], 'cache.gpkg'), - test_config['base_dir']) - create_app(test_config) ---- a/mapproxy/test/unit/test_cache_geopackage.py -+++ b/mapproxy/test/unit/test_cache_geopackage.py -@@ -29,6 +29,7 @@ from mapproxy.grid import tile_grid, Til - from mapproxy.test.unit.test_cache_tile import TileCacheTestBase - - from nose.tools import eq_ -+from nose.plugins.skip import SkipTest - - class TestGeopackageCache(TileCacheTestBase): - -@@ -190,6 +191,10 @@ class TestGeopackageCacheInitErrors(obje - 'fixture'), - 'cache.gpkg') - table_name = 'cache' -+ -+ if not os.path.exists(gpkg_file): -+ raise SkipTest -+ - try: - GeopackageCache(gpkg_file, TileGrid(srs=4326), table_name) - except ValueError as ve: -@@ -202,6 +207,10 @@ class TestGeopackageCacheInitErrors(obje - 'fixture'), - 'cache.gpkg') - table_name = 'cache' -+ -+ if not os.path.exists(gpkg_file): -+ raise SkipTest -+ - try: - GeopackageCache(gpkg_file, TileGrid(srs=900913, tile_size=(512, 512)), table_name) - except ValueError as ve: -@@ -214,6 +223,10 @@ class TestGeopackageCacheInitErrors(obje - 'fixture'), - 'cache.gpkg') - table_name = 'cache' -+ -+ if not os.path.exists(gpkg_file): -+ raise SkipTest -+ - try: - GeopackageCache(gpkg_file, TileGrid(srs=900913, res=[1000, 100, 10]), table_name) - except ValueError as ve: diff -Nru mapproxy-1.11.0/debian/rules mapproxy-1.12.0/debian/rules --- mapproxy-1.11.0/debian/rules 2017-11-20 15:49:37.000000000 +0000 +++ mapproxy-1.12.0/debian/rules 2019-09-01 11:07:44.000000000 +0000 @@ -11,7 +11,7 @@ MANPAGES := $(wildcard debian/man/*.*.xml) %: - dh $@ --with python2,python3,sphinxdoc --buildsystem=pybuild + dh $@ --with python3,sphinxdoc --buildsystem=pybuild override_dh_clean: dh_clean debian/man/*.1 @@ -33,7 +33,7 @@ (cd doc && PYTHONPATH=$(CURDIR) $(MAKE) html) override_dh_auto_test: - OFFLINE_TESTS=1 dh_auto_test || echo "Ignoring test failures" + dh_auto_test || echo "Ignoring test failures" override_dh_auto_install: PYBUILD_INSTALL_ARGS="--install-scripts=/usr/lib/{package}" dh_auto_install @@ -41,41 +41,6 @@ # Remove duplicate license file $(RM) debian/*/usr/lib/python*/dist-packages/mapproxy/image/fonts/LICENSE - for V in `pyversions -v -s`; do \ - for F in DejaVuSans.ttf DejaVuSansMono.ttf; do \ - $(RM) debian/python-$(PYBUILD_NAME)/usr/lib/python$$V/dist-packages/mapproxy/image/fonts/$$F ; \ - ln -s /usr/share/fonts/truetype/dejavu/$$F \ - debian/python-$(PYBUILD_NAME)/usr/lib/python$$V/dist-packages/mapproxy/image/fonts/$$F ; \ - done ; \ - if [ ! -e debian/python-$(PYBUILD_NAME)/usr/share/python-$(PYBUILD_NAME)/service/templates ]; then \ - mkdir -p debian/python-$(PYBUILD_NAME)/usr/share/python-$(PYBUILD_NAME)/service ; \ - mv debian/python-$(PYBUILD_NAME)/usr/lib/python$$V/dist-packages/mapproxy/service/templates \ - debian/python-$(PYBUILD_NAME)/usr/share/python-$(PYBUILD_NAME)/service/ ; \ - else \ - $(RM) -r debian/python-$(PYBUILD_NAME)/usr/lib/python$$V/dist-packages/mapproxy/service/templates ; \ - fi ; \ - ln -s /usr/share/python-$(PYBUILD_NAME)/service/templates \ - debian/python-$(PYBUILD_NAME)/usr/lib/python$$V/dist-packages/mapproxy/service/templates ; \ - if [ ! -e debian/python-$(PYBUILD_NAME)/usr/share/python-$(PYBUILD_NAME)/test/schemas ]; then \ - mkdir -p debian/python-$(PYBUILD_NAME)/usr/share/python-$(PYBUILD_NAME)/test ; \ - mv debian/python-$(PYBUILD_NAME)/usr/lib/python$$V/dist-packages/mapproxy/test/schemas \ - debian/python-$(PYBUILD_NAME)/usr/share/python-$(PYBUILD_NAME)/test/ ; \ - else \ - $(RM) -r debian/python-$(PYBUILD_NAME)/usr/lib/python$$V/dist-packages/mapproxy/test/schemas ; \ - fi ; \ - ln -s /usr/share/python-$(PYBUILD_NAME)/test/schemas \ - debian/python-$(PYBUILD_NAME)/usr/lib/python$$V/dist-packages/mapproxy/test/schemas ; \ - if [ ! -e debian/python-$(PYBUILD_NAME)/usr/share/python-$(PYBUILD_NAME)/test/system/fixture/cache_data ]; then \ - mkdir -p debian/python-$(PYBUILD_NAME)/usr/share/python-$(PYBUILD_NAME)/test/system/fixture ; \ - mv debian/python-$(PYBUILD_NAME)/usr/lib/python$$V/dist-packages/mapproxy/test/system/fixture/cache_data \ - debian/python-$(PYBUILD_NAME)/usr/share/python-$(PYBUILD_NAME)/test/system/fixture/ ; \ - else \ - $(RM) -r debian/python-$(PYBUILD_NAME)/usr/lib/python$$V/dist-packages/mapproxy/test/system/fixture/cache_data ; \ - fi ; \ - ln -s /usr/share/python-$(PYBUILD_NAME)/test/system/fixture/cache_data \ - debian/python-$(PYBUILD_NAME)/usr/lib/python$$V/dist-packages/mapproxy/test/system/fixture/cache_data ; \ - done - for V in `py3versions -v -s`; do \ for F in DejaVuSans.ttf DejaVuSansMono.ttf; do \ $(RM) debian/python3-$(PYBUILD_NAME)/usr/lib/python$$V/dist-packages/mapproxy/image/fonts/$$F ; \ @@ -114,3 +79,4 @@ override_dh_install: dh_install --list-missing + $(RM) debian/*/usr/share/python*-mapproxy/test/schemas/*/*/ReadMe.txt diff -Nru mapproxy-1.11.0/debian/source/lintian-overrides mapproxy-1.12.0/debian/source/lintian-overrides --- mapproxy-1.11.0/debian/source/lintian-overrides 1970-01-01 00:00:00.000000000 +0000 +++ mapproxy-1.12.0/debian/source/lintian-overrides 2018-07-31 19:42:19.000000000 +0000 @@ -0,0 +1,3 @@ +# Not worth the effort +testsuite-autopkgtest-missing + diff -Nru mapproxy-1.11.0/debian/tests/control mapproxy-1.12.0/debian/tests/control --- mapproxy-1.11.0/debian/tests/control 2017-06-21 16:04:19.000000000 +0000 +++ mapproxy-1.12.0/debian/tests/control 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -# Test installability -Depends: @ -Test-Command: /bin/true diff -Nru mapproxy-1.11.0/debian/watch mapproxy-1.12.0/debian/watch --- mapproxy-1.11.0/debian/watch 2016-08-22 12:38:54.000000000 +0000 +++ mapproxy-1.12.0/debian/watch 2019-09-01 11:07:44.000000000 +0000 @@ -4,4 +4,4 @@ uversionmangle=s/(\d)[_\.\-\+]?((RC|rc|pre|dev|beta|alpha)\d*)$/$1~$2/;s/RC/rc/,\ filenamemangle=s/(?:.*?\/)?(?:rel|v|mapproxy)?[\-\_]?(\d\S+)\.(tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz)))/mapproxy-$1.$2/ \ https://github.com/mapproxy/mapproxy/releases \ -(?:.*?/)?(?:rel|v|mapproxy)?[\-\_]?(\d\S+)\.(?:tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz))) +(?:.*?/archive/)?(?:rel|v|mapproxy)?[\-\_]?(\d\S+)\.(?:tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz))) diff -Nru mapproxy-1.11.0/doc/auth.rst mapproxy-1.12.0/doc/auth.rst --- mapproxy-1.11.0/doc/auth.rst 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/doc/auth.rst 2019-08-30 07:34:08.000000000 +0000 @@ -493,7 +493,9 @@ The WMTS authorization is similar to the TMS authorization, including the ``limited_to`` option. -The WMTS service uses ``wmts`` as the service string for all authorization requests. +The WMTS service uses ``wmts`` as the service string for tile requests and ``wmts.featureinfo`` for feature info requests. + +.. versionadded:: 1.12 ``wmts.featureinfo`` Demo Service diff -Nru mapproxy-1.11.0/doc/caches.rst mapproxy-1.12.0/doc/caches.rst --- mapproxy-1.11.0/doc/caches.rst 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/doc/caches.rst 2019-08-30 07:34:08.000000000 +0000 @@ -286,7 +286,7 @@ Requirements ------------ -You will need the `Python Riak client `_ version 2.4.2 or older. You can install it in the usual way, for example with ``pip install riak==2.4.2``. Environments with older version must be upgraded with ``pip install -U riak==2.4.2``. Python library depends on packages `python-dev`, `libffi-dev` and `libssl-dev`. +You will need the `Python Riak client `_ version 2.4.2 or older. You can install it in the usual way, for example with ``pip install riak==2.4.2``. Environments with older version must be upgraded with ``pip install -U riak==2.4.2``. Python library depends on packages `python-dev`, `libffi-dev` and `libssl-dev`. Configuration ------------- @@ -347,7 +347,7 @@ Requirements ------------ -You will need the `Python Redis client `_. You can install it in the usual way, for example with ``pip install redis``. +You will need the `Python Redis client `_. You can install it in the usual way, for example with ``pip install redis``. Configuration ------------- @@ -434,13 +434,16 @@ .. versionadded:: 1.10.0 -Store tiles in a `Amazon Simple Storage Service (S3) `_. +.. versionadded:: 1.11.0 + ``region_name``, ``endpoint_url`` and ``access_control_list`` + +Store tiles in a `Amazon Simple Storage Service (S3) `_ or any other S3 compatible object storage. Requirements ------------ -You will need the Python `boto3 `_ package. You can install it in the usual way, for example with ``pip install boto3``. +You will need the Python `boto3 `_ package. You can install it in the usual way, for example with ``pip install boto3``. Configuration ------------- @@ -454,6 +457,15 @@ Optional profile name for `shared credentials `_ for this cache. Alternative methods of authentification are using the ``AWS_ACCESS_KEY_ID`` and ``AWS_SECRET_ACCESS_KEY`` environmental variables, or by using an `IAM role `_ when using an Amazon EC2 instance. You can set the default profile with ``globals.cache.s3.profile_name``. +``region_name``: + Optional name of the region. You can set the default region_name with ``globals.cache.s3.region_name`` + +``endpoint_url``: + Optional endpoint_url for the S3. You can set the default endpoint_url with ``globals.cache.s3.endpoint_url``. + +``access_control_list``: + Optional access control list for the S3. You can set the default access_control_list with ``globals.cache.s3.access_control_list``. + ``directory``: Base directory (path) where all tiles are stored. @@ -482,6 +494,28 @@ profile_name: default +Example usage with DigitalOcean Spaces +-------------------------------------- + +:: + + cache: + my_layer_20110501_epsg_4326_cache_out: + sources: [my_layer_20110501_cache] + cache: + type: s3 + directory: /1.0.0/my_layer/default/20110501/4326/ + bucket_name: my-s3-tiles-cache + + globals: + cache: + s3: + profile_name: default + region_name: nyc3 + endpoint_url: https://nyc3.digitaloceanspaces.com + access_control_list: public-read + + .. _cache_compact: diff -Nru mapproxy-1.11.0/doc/configuration_examples.rst mapproxy-1.12.0/doc/configuration_examples.rst --- mapproxy-1.11.0/doc/configuration_examples.rst 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/doc/configuration_examples.rst 2019-08-30 07:34:08.000000000 +0000 @@ -393,6 +393,7 @@ res_factor: 1.6 The third options is a convenient variation of the previous option. A factor of 1.41421, the square root of two, would get resolutions of 10, 7.07, 5, 3.54, 2.5,…. Notice that every second resolution is identical to the power-of-two resolutions. This comes in handy if you use the layer not only in classic WMS clients but also want to use it in tile-based clients like OpenLayers, which only request in these resolutions. + :: grids: @@ -469,6 +470,7 @@ If you do not want to cache data but still want to use MapProxy's ability to reproject WMS layers on the fly, you can use a direct layer. Add your source directly to your layer instead of a cache. You should explicitly define the SRS the source WMS supports. Requests in other SRS will be reprojected. You should specify at least one geographic and one projected SRS to limit the distortions from reprojection. + :: layers: @@ -504,6 +506,9 @@ MapProxy will mark all layers that use this source as ``queryable``. It also works for sources that are used with caching. +FeatureInfo support is enabled by default for WMS. For :ref:`WMTS you need to enable FeatureInfo queries by configuring the supported formats `. + + .. note:: The more advanced features :ref:`require the lxml library `. Concatenation @@ -572,7 +577,7 @@ Lets assume we have two WMS sources where we have no control over the format of the feature info responses. -One source only offers HTML feature information. The XSLT script extracts data from a table. We force the ``INFO_FORMAT`` to HTML, so that MapProxy will not query another format. +One source only offers HTML feature information. The XSLT script extracts data from a table. We force the WMS ``INFO_FORMAT`` to HTML with the ``featureinfo_format`` option, so that MapProxy will not query another format. The XSLT script returns XML and not HTML. We configure this with the ``featureinfo_out_format`` option. :: @@ -582,10 +587,12 @@ featureinfo: true featureinfo_xslt: ./html_in.xslt featureinfo_format: text/html + featureinfo_out_format: text/xml req: [...] The second source supports XML feature information. The script converts the XML data to the same format as the HTML script. This service uses WMS 1.3.0 and the format is ``text/xml``. + :: fi_source: @@ -706,16 +713,16 @@ Access sources through HTTP proxy ================================= -MapProxy can use an HTTP proxy to make requests to your sources, if your system does not allow direct access to the source. You need to set the ``http_proxy`` environment variable to the proxy URL. This also applies if you install MapProxy with ``pip`` or ``easy_install``. +MapProxy can use an HTTP proxy to make requests to your sources, if your system does not allow direct access to the source. You need to set the ``http_proxy`` and ``https_proxy`` environment variable to the proxy URL. This also applies if you install MapProxy with ``pip``. On Linux/Unix:: - $ export http_proxy="http://example.com:3128" + $ export http_proxy="http://example.com:3128" https_proxy="http://example.com:3128" $ mapproxy-util serve-develop mapproxy.yaml On Windows:: - c:\> set http_proxy="http://example.com:3128" + c:\> set http_proxy="http://example.com:3128" https_proxy="http://example.com:3128" c:\> mapproxy-util serve-develop mapproxy.yaml @@ -723,6 +730,7 @@ import os os.environ["http_proxy"] = "http://example.com:3128" + os.environ["https_proxy"] = "http://example.com:3128" Add a username and password to the URL if your HTTP proxy requires authentication. For example ``http://username:password@example.com:3128``. @@ -730,7 +738,6 @@ $ export no_proxy="localhost,127.0.0.1,196.168.1.99" -``no_proxy`` is available since Python 2.6.3. .. _paster_urlmap: diff -Nru mapproxy-1.11.0/doc/configuration.rst mapproxy-1.12.0/doc/configuration.rst --- mapproxy-1.11.0/doc/configuration.rst 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/doc/configuration.rst 2019-08-30 07:34:08.000000000 +0000 @@ -487,6 +487,32 @@ You can limit until which resolution MapProxy should cache data with these two options. Requests below the configured resolution or level will be passed to the underlying source and the results will not be stored. The resolution of ``use_direct_from_res`` should use the units of the first configured grid of this cache. This takes only effect when used in WMS services. +``upscale_tiles`` and ``downscale_tiles`` +""""""""""""""""""""""""""""""""""""""""" + +MapProxy is able to create missing tiles by rescaling tiles from zoom levels below or above. + +MapProxy will scale up tiles from one or more zoom levels above (with lower resolutions) if you set ``upscale_tiles`` to 1 or higher. The value configures by how many zoom levels MapProxy can search for a proper tile. Higher values allow more blurry results. + +You can use ``upscale_tiles`` if you want to provide tiles or WMS responses in a higher resolution then your available cache. This also works with partially seeded caches, eg. where you have an arial image cache of 20cm, with some areas also in 10cm resolution. ``upscale_tiles`` allows you to provide responses for 10cm requests in all areas, allways returning the best available data. + +MapProxy will scale down tiles from one or more zoom levels below (with higher resolutions) if you set ``downscale_tiles`` to 1 or higher. The value configures by how many zoom levels MapProxy can search for a proper tile. Note that the number of tiles growth exponentialy. Typically, a single tile can be downscaled from four tiles of the next zoom level. Downscaling from two levels below requires 16 tiles, three levels below requires 64, etc.. A larger WMS request can quickly accumulate thousands of tiles required for downscaling. It is therefore `not` recommended to use ``downscale_tiles`` values larger then one. + +You can use ``downscale_tiles`` to fill a cache for a source that only provides data for higher resolutions. + +``mapproxy-seed`` will seed each level independently for caches with ``upscale_tiles`` or ``downscale_tiles``. It will start with the highest zoom level for ``downscale_tiles``, so that tiles in the next (lower) zoom levels can be created by downscaling the already created tiles. It will start in the lowest zoom level for ``upscale_tiles``, so that tiles in the next (higher) zoom levels can be created by upscaling the already creates tiles. + +A transparent tile is returned if no tile is found within the configured ``upscale_tiles`` or ``downscale_tiles`` range. + + +To trigger the rescaling behaviour, a tile needs to be missing in the cache and MapProxy needs to be unable to fetch the tile from the source. MapProxy is unable to fetch the tile if the cache has no sources, or if all sources are either ``seed_only`` or limited to a different resolution (``min_res``/``max_res``). + + +``cache_rescaled_tiles`` +"""""""""""""""""""""""" + +Tiles created by the ``upscale_tiles`` or ``downscale_tiles`` option are only stored in the cache if this option is set to true. + ``disable_storage`` """""""""""""""""""" @@ -858,6 +884,29 @@ ``srs`` """"""" +``preferred_src_proj`` + This option allows you to control which source projection MapProxy should use + when it needs to reproject an image. + + When you make a request for a projection that is not supported by your cache (tile grid) or by your source (``supported_srs``), then MapProxy will reproject the image from the `best` available projection. By default, the `best` available projection is the first supported projection by your cache or source that is also either projected or geographic. + + You can change this behavior with ``preferred_src_proj``. For example, you can configure that MapProxy should prefer similar projections from neighboring zones over Webmercator. + + ``preferred_src_proj`` is a dictionary with the target EPSG code (i.e. the SRS requested by the user) and a list of preferred source EPSG codes. + + With the following configuration, WMS requests for EPSG:25831 are served from a cache with EPSG:25832, if there is no cache for EPSG:25831. + :: + + srs: + preferred_src_proj: + 'EPSG:25831': ['EPSG:25832', 'EPSG:3857'] + 'EPSG:25832': ['EPSG:25831', 'EPSG:25833', 'EPSG:3857'] + 'EPSG:25833': ['EPSG:25832'', 'EPSG:3857'] + 'EPSG:31466': ['EPSG:25831', 'EPSG:25832', 'EPSG:3857'] + 'EPSG:31467': ['EPSG:25832', 'EPSG:25833', 'EPSG:25831', 'EPSG:3857'] + + .. versionadded:: 1.12.0 + ``proj_data_dir`` MapProxy uses Proj4 for all coordinate transformations. If you need custom projections or need to tweak existing definitions (e.g. add towgs parameter set) you can point @@ -866,6 +915,7 @@ The configured path can be absolute or relative to the mapproxy.yaml. + .. _axis_order: ``axis_order_ne`` and ``axis_order_en`` @@ -1048,6 +1098,12 @@ The algorithm used to quantize (reduce) the image colors. Quantizing is used for GIF and paletted PNG images. Available quantizers are ``mediancut`` and ``fastoctree``. ``fastoctree`` is much faster and also supports 8bit PNG with full alpha support, but the image quality can be better with ``mediancut`` in some cases. The quantizing is done by the Python Image Library (PIL). ``fastoctree`` is a `new quantizer `_ that is only available in Pillow >=2.0. See :ref:`installation of PIL`. +``tiff_compression`` + Enable compression for TIFF images. Available compression methods are `tiff_lzw` for lossless LZW compression, `jpeg` for JPEG compression and `raw` for no compression (default). You can use the ``jpeg_quality`` option to tune the image quality for JPEG compressed TIFFs. Requires Pillow >= 6.1.0. + + .. versionadded:: 1.12.0 + + Global """""" diff -Nru mapproxy-1.11.0/doc/conf.py mapproxy-1.12.0/doc/conf.py --- mapproxy-1.11.0/doc/conf.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/doc/conf.py 2019-08-30 07:34:08.000000000 +0000 @@ -49,9 +49,9 @@ # built documents. # # The short X.Y version. -version = '1.11' +version = '1.12' # The full version, including alpha/beta/rc tags. -release = '1.11.0' +release = '1.12.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff -Nru mapproxy-1.11.0/doc/coverages.rst mapproxy-1.12.0/doc/coverages.rst --- mapproxy-1.11.0/doc/coverages.rst 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/doc/coverages.rst 2019-08-30 07:34:08.000000000 +0000 @@ -86,7 +86,7 @@ ``where``: Restrict which polygons should be loaded from the datasource. Either a simple where - statement (e.g. ``'CNTRY_NAME="Germany"'``) or a full select statement. Refer to the + statement (e.g. ``"CNTRY_NAME='Germany'"``) or a full select statement. Refer to the `OGR SQL support documentation `_. If this option is unset, the first layer from the datasource will be used. @@ -193,7 +193,7 @@ coverages: germany: datasource: 'shps/world_boundaries_m.shp' - where: 'CNTRY_NAME = "Germany"' + where: CNTRY_NAME = 'Germany' srs: 'EPSG:900913' .. index:: PostGIS, PostgreSQL diff -Nru mapproxy-1.11.0/doc/deployment.rst mapproxy-1.12.0/doc/deployment.rst --- mapproxy-1.11.0/doc/deployment.rst 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/doc/deployment.rst 2019-08-30 07:34:08.000000000 +0000 @@ -27,7 +27,7 @@ .. cmdoption:: --debug - Start MapProxy in debug mode. If you have installed Werkzeug_ (recommended) or Paste_, you will get an interactive traceback in the web browser on any unhandled exception (internal error). + Start MapProxy in debug mode. If you have installed Werkzeug_, you will get an interactive traceback in the web browser on any unhandled exception (internal error). .. note:: This server is sufficient for local testing of the configuration, but it is `not` stable for production or load testing. @@ -49,8 +49,6 @@ Both approaches require a configuration that maps your MapProxy configuration with the MapProxy application. You can write a small script file for that. -Running MapProxy as a FastCGI server behind HTTP server, a third option, is no longer advised for new setups since the FastCGI package (flup) is no longer maintained and the Python HTTP server improved significantly. - .. _server_script: Server script @@ -67,7 +65,7 @@ from mapproxy.wsgiapp import make_wsgi_app application = make_wsgi_app('examples/minimal/etc/mapproxy.yaml') -This is sufficient for embedding MapProxy with ``mod_wsgi`` or for starting it with Python HTTP servers like ``gunicorn`` (see further below). You can extend this script to setup logging or to set environment variables. +This is sufficient for embedding MapProxy with ``mod_wsgi`` or for starting it with Python HTTP servers like ``waitress`` (see further below). You can extend this script to setup logging or to set environment variables. You can enable MapProxy to automatically reload the configuration if it changes:: @@ -133,49 +131,27 @@ Python HTTP Server ~~~~~~~~~~~~~~~~~~ -You need start these servers in the background on start up. It is recommended to create an init script for that or to use tools like upstart_ or supervisord_. +You need start these servers in the background on start up. It is recommended to start it from systemd or upstart. -Gunicorn +Waitress """""""" -Gunicorn_ is a Python WSGI HTTP server for UNIX. Gunicorn use multiple processes but the process number is fixed. The default worker is synchronous, meaning that a process is blocked while it requests data from another server for example. You need to choose an asynchronous worker like eventlet_. +Waitress_ is a production-quality pure-Python WSGI server with very acceptable performance. It runs on Unix and Windows. -You need a server script that creates the MapProxy application (see :ref:`above `). The script needs to be in the directory from where you start ``gunicorn`` and it needs to end with ``.py``. +You need a server script that creates the MapProxy application (see :ref:`above `). The script needs to be in the directory from where you start ``waitress`` and it needs to end with ``.py``. -To start MapProxy with the Gunicorn web server with four processes, the eventlet worker and our server script (without ``.py``):: +To start MapProxy with Waitress and our server script (without ``.py``):: cd /path/of/config.py/ - gunicorn -k eventlet -w 4 -b :8080 config:application --no-sendfile - - -An example upstart script (``/etc/init/mapproxy.conf``) might look like:: - - start on runlevel [2345] - stop on runlevel [!2345] - - respawn + waitress --listen 127.0.0.1:8080 config:application - setuid mapproxy - setgid mapproxy - chdir /etc/opt/mapproxy - - exec /opt/mapproxy/bin/gunicorn -k eventlet -w 8 -b :8080 \ - --no-sendfile \ - application \ - >>/var/log/mapproxy/gunicorn.log 2>&1 - - -Spawning -"""""""" - -Spawning_ is another Python WSGI HTTP server for UNIX that supports multiple processes and multiple threads. +uWSGI +""""" -:: +uWSGI is another production-quality WSGI server. It is highly configurable and offers high performance (by running on multiple processors). - cd /path/of/config.py/ - spawning config.application --threads=8 --processes=4 \ - --port=8080 +The `uWSGI documentation provides a quickstart `_. HTTP Proxy @@ -224,16 +200,6 @@ You need to make sure that both modules are loaded. The ``Host`` is already set to the right value by default. -Other deployment options ------------------------- - -Refer to http://wsgi.readthedocs.org/en/latest/servers.html for a list of some available WSGI servers. - -FastCGI -~~~~~~~ - -.. note:: Running MapProxy as a FastCGI server behind HTTP server is no longer advised for new setups since the used Python package (flup) is no longer maintained. Please refer to the `MapProxy 1.5.0 deployment documentation for more information on FastCGI `_. - Performance ----------- @@ -259,17 +225,10 @@ .. _mod_proxy: http://httpd.apache.org/docs/current/mod/mod_proxy.html .. _Varnish: http://www.varnish-cache.org/ .. _werkzeug: http://pypi.python.org/pypi/Werkzeug -.. _paste: http://pypi.python.org/pypi/Paste -.. _gunicorn: http://gunicorn.org/ -.. _Spawning: http://pypi.python.org/pypi/Spawning +.. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/ +.. _Waitress: https://docs.pylonsproject.org/projects/waitress/en/stable/ .. _FastCGI: http://www.fastcgi.com/ -.. _flup: http://pypi.python.org/pypi/flup -.. _mod_fastcgi: http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html -.. _mod_fcgid: http://httpd.apache.org/mod_fcgid/ -.. _eventlet: http://pypi.python.org/pypi/eventlet .. _Apache: http://httpd.apache.org/ -.. _upstart: http://upstart.ubuntu.com/ -.. _supervisord: http://supervisord.org/ Logging ------- diff -Nru mapproxy-1.11.0/doc/development.rst mapproxy-1.12.0/doc/development.rst --- mapproxy-1.11.0/doc/development.rst 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/doc/development.rst 2019-08-30 07:34:08.000000000 +0000 @@ -44,16 +44,16 @@ MapProxy contains lots of automatic tests. If you don't count in the ``mapproxy-seed``-tool and the WSGI application, the test coverage is around 95%. We want to keep this number high, so all new developments should include some tests. -MapProxy uses `Nose`_ as a test loader and runner. To install Nose and all further test dependencies call:: +MapProxy uses `pytest`_ as a test loader and runner. - pip install -r requirements-tests.txt + pip install pytest To run the actual tests call:: - nosetests + pytest -.. _`Nose`: http://somethingaboutorange.com/mrl/projects/nose/ +.. _`pytest`: https://pytest.org/ Available tests """"""""""""""" @@ -80,9 +80,6 @@ The preferred medium for all MapProxy related discussions is our mailing list mapproxy@lists.osgeo.org You must `subscribe `_ to the list before you can write. The archive is `available here `_. -IRC -""" -There is also a channel on `Freenode `_: ``#mapproxy``. It is a quiet place but you might find someone during business hours (central european time). Tips on development ------------------- @@ -91,14 +88,11 @@ Before you start hacking on MapProxy you should install it in development-mode. In the root directory of MapProxy call ``pip install -e ./``. Instead of installing and thus copying MapProxy into your `virtualenv`, this will just link to your source directory. If you now start MapProxy, the source from your MapProxy directory will be used. Any change you do in the code will be available if you restart MapProxy. If you use the ``mapproxy-util serve-develop`` command, any change in the source will issue a reload of the MapProxy server. -.. todo:: - - Describe egg:Paste#evalerror - Coding Style Guide ------------------ MapProxy generally follows the `Style Guide for Python Code`_. With the only exception that we permit a line width of about 90 characters. +New files should be auto-formatted with `black `_. -.. _`Style Guide for Python Code`: http://www.python.org/dev/peps/pep-0008/ \ No newline at end of file +.. _`Style Guide for Python Code`: http://www.python.org/dev/peps/pep-0008/ diff -Nru mapproxy-1.11.0/doc/install.rst mapproxy-1.12.0/doc/install.rst --- mapproxy-1.11.0/doc/install.rst 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/doc/install.rst 2019-08-30 07:34:08.000000000 +0000 @@ -3,13 +3,15 @@ This tutorial guides you to the MapProxy installation process on Unix systems. For Windows refer to :doc:`install_windows`. -This tutorial was created and tested with Debian 5.0/6.0 and Ubuntu 10.04 LTS, if you're installing MapProxy on a different system you might need to change some package names. +This tutorial was created and tested with Debian and Ubuntu, if you're installing MapProxy on a different system you might need to change some package names. -MapProxy is `registered at the Python Package Index `_ (PyPI). If you have installed Python setuptools (``python-setuptools`` on Debian) you can install MapProxy with ``sudo easy_install MapProxy``. +MapProxy is `registered at the Python Package Index `_ (PyPI). If you have Python 2.7.9 or higher, you can install MapProxy with:: -This is really easy `but` we recommend to install MapProxy into a `virtual Python environment`_. A ``virtualenv`` is a self-contained Python installation where you can install arbitrary Python packages without affecting the system installation. You also don't need root permissions for the installation. + sudo python -m pip MapProxy -`Read about virtualenv `_ if you want to know more about the benefits. +This is really, easy `but` we recommend to install MapProxy into a `virtual Python environment`_. A ``virtualenv`` is a self-contained Python installation where you can install arbitrary Python packages without affecting the system installation. You also don't need root permissions for the installation. + +`Read about virtualenv `_ if you want to know more about the benefits. .. _`virtual Python environment`: http://guide.python-distribute.org/virtualenv.html @@ -17,49 +19,40 @@ Create a new virtual environment -------------------------------- -``virtualenv`` is available as ``python-virtualenv`` on most Linux systems. You can also download a self-contained version:: - - wget https://github.com/pypa/virtualenv/raw/master/virtualenv.py +``virtualenv`` is available as ``python-virtualenv`` on most Linux systems. You can also `install Virtualenv from source `_. To create a new environment with the name ``mapproxy`` call:: virtualenv --system-site-packages mapproxy - # or - python virtualenv.py --system-site-packages mapproxy You should now have a Python installation under ``mapproxy/bin/python``. -.. note:: Newer versions of virtualenv will use your Python system packages (like ``python-imaging`` or ``python-yaml``) only when the virtualenv was created with the ``--system-site-packages`` option. If your (older) version of virtualenv does not have this option, then it will behave that way by default. +.. note:: Virtualenv will use your Python system packages (like ``python-imaging`` or ``python-yaml``) only when the virtualenv was created with the ``--system-site-packages`` option. You need to either prefix all commands with ``mapproxy/bin``, set your ``PATH`` variable to include the bin directory or `activate` the virtualenv with:: source mapproxy/bin/activate -This will change the ``PATH`` for you and will last for that terminal session. +This will change the ``PATH`` for you `current` session. -.. _`distribute`: http://packages.python.org/distribute/ Install Dependencies -------------------- -MapProxy is written in Python, thus you will need a working Python installation. MapProxy works with Python 2.7, 3.3 and 3.4 which should already be installed with most Linux distributions. Python 2.6 should still work, but it is no longer officially supported. +MapProxy is written in Python, thus you will need a working Python installation. MapProxy works with Python 2.7 and 3.4 or higher, which should already be installed with most Linux distributions. -MapProxy has some dependencies, other libraries that are required to run. There are different ways to install each dependency. Read :ref:`dependency_details` for a list of all required and optional dependencies. +MapProxy requires a few third-party libraries that are required to run. There are different ways to install each dependency. Read :ref:`dependency_details` for a list of all required and optional dependencies. Installation ^^^^^^^^^^^^ On a Debian or Ubuntu system, you need to install the following packages:: - sudo aptitude install python-imaging python-yaml libproj0 + sudo apt-get install python-pil python-yaml libproj12 To get all optional packages:: - sudo aptitude install libgeos-dev python-lxml libgdal-dev python-shapely - -.. note:: - Check that the ``python-shapely`` package is ``>=1.2``, if it is not - you need to install it with ``pip install Shapely``. + sudo apt-get install libgeos-dev python-lxml libgdal-dev python-shapely .. _dependency_details: @@ -68,17 +61,17 @@ libproj ~~~~~~~ -MapProxy uses the Proj4 C Library for all coordinate transformation tasks. It is included in most distributions as ``libproj0``. +MapProxy uses the Proj4 C Library for all coordinate transformation tasks. It is included in most distributions as ``libproj`` or ``libprojXX`` where ``XX`` is a number. .. _dependencies_pil: Pillow ~~~~~~ -Pillow, the successor of the Python Image Library (PIL), is used for the image processing and it is included in most distributions as ``python-imaging``. Please make sure that you have Pillow installed as MapProxy is no longer compatible with the original PIL. The version of ``python-imaging`` should be >=2. +Pillow, the successor of the Python Image Library (PIL), is used for the image processing and it is included in most distributions as ``python-pil`` or ``python-imaging``. Please make sure that you have Pillow installed as MapProxy is no longer compatible with the original PIL. The version of ``python-imaging`` should be >=2. You can install a new version of Pillow from source with:: - sudo aptitude install build-essential python-dev libjpeg-dev \ + sudo apt-get install build-essential python-dev libjpeg-dev \ zlib1g-dev libfreetype6-dev pip install Pillow @@ -108,7 +101,7 @@ Install MapProxy ---------------- -Your virtual environment should already contain `pip`_, a tool to install Python packages. If not, ``easy_install pip`` is enough to get it. +Your virtual environment should contains `pip`_, a tool to install Python packages. To install you need to call:: @@ -116,22 +109,22 @@ You specify the release version of MapProxy. E.g.:: - pip install MapProxy==1.8.0 + pip install MapProxy==1.10.0 -or to get the latest 1.8.0 version:: +or to get the latest 1.10.0 version:: - pip install "MapProxy>=1.8.0,<=1.8.99" + pip install "MapProxy>=1.10.0,<=1.10.99" To check if the MapProxy was successfully installed, you can call the `mapproxy-util` command. :: mapproxy-util --version -.. _`pip`: http://pip.openplans.org/ +.. _`pip`: https://pip.pypa.io/en/stable/ .. note:: - ``pip`` and ``easy_install`` will download packages from the `Python Package Index `_ and therefore they require full internet access. You need to set the ``http_proxy`` environment variable if you only have access to the internet via an HTTP proxy. See :ref:`http_proxy` for more information. + ``pip`` will download packages from the `Python Package Index `_ and therefore require full internet access. You need to set the ``https_proxy`` environment variable if you only have access to the internet via an HTTP proxy. See :ref:`http_proxy` for more information. .. _create_configuration: @@ -159,7 +152,7 @@ MapProxy comes with a demo service that lists all configured WMS and TMS layers. You can access that service at http://localhost:8080/demo/ -.. _`Omniscale OpenStreetMap WMS`: http://osm.omniscale.de/ +.. _`Omniscale OpenStreetMap WMS`: https://maps.omniscale.com/ Upgrade @@ -186,6 +179,6 @@ Changes ^^^^^^^ -New releases of MapProxy are backwards compatible with older configuration files. MapProxy will issue warnings on startup if a behavior will change in the next releases. You are advised to upgrade in single release steps (e.g. 1.2.0 to 1.3.0 to 1.4.0) and to check the output of ``mapproxy-util serve-develop`` for any warnings. You should also refer to the Changes Log of each release to see if there is anything to pay attention for. +New releases of MapProxy are backwards compatible with older configuration files. MapProxy will issue warnings on startup if a behavior will change in the next releases. You are advised to upgrade in single release steps (e.g. 1.9.0 to 1.10.0 to 1.11.0) and to check the output of ``mapproxy-util serve-develop`` for any warnings. You should also refer to the Changes Log of each release to see if there is anything to pay attention for. -If you upgrade from 0.8, please read the old mirgation documentation `_. +If you upgrade from 0.8, please read the `old mirgation documentation `_. diff -Nru mapproxy-1.11.0/doc/install_windows.rst mapproxy-1.12.0/doc/install_windows.rst --- mapproxy-1.11.0/doc/install_windows.rst 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/doc/install_windows.rst 2019-08-30 07:34:08.000000000 +0000 @@ -1,15 +1,13 @@ Installation on Windows ======================= -At frist you need a working Python installation. You can download Python from: https://www.python.org/download/. MapProxy requires Python 2.7, 3.3, 3.4, 3.5 or 3.6. Python 2.6 should still work, but it is no longer officially supported. We would recommend the latest 2.7 version available. +At frist you need a working Python installation. You can download Python from: https://www.python.org/download/. MapProxy requires Python 2.7, 3.4 or higher. Virtualenv ---------- -*If* you are using your Python installation for other applications as well, then we advise you to install MapProxy into a `virtual Python environment`_ to avoid any conflicts with different dependencies. *You can skip this if you only use the Python installation for MapProxy.* -`Read about virtualenv `_ if you want to now more about the benefits. - -.. _`virtual Python environment`: http://guide.python-distribute.org/virtualenv.html +*If* you are using your Python installation for other applications as well, then we advise you to install MapProxy into a virtual Python environment to avoid any conflicts with different dependencies. *You can skip this if you only use the Python installation for MapProxy.* +`Read about virtualenv `_ if you want to know more about the benefits. To create a new virtual environment for your MapProxy installation and to activate it go to the command line and call:: @@ -89,7 +87,7 @@ Platform dependent packages --------------------------- -``pip`` downloads all packages from https://pypi.python.org/, but not all platform combinations might be available as a binary package, especially if you run a 64bit version of Python. +``pip`` downloads all packages from https://pypi.org/, but not all platform combinations might be available as a binary package, especially if you run a 64bit version of Python. If you run into trouble during installation, because it is trying to compile something (e.g. complaining about ``vcvarsall.bat``), you should look at Christoph Gohlke's `Unofficial Windows Binaries for Python Extension Packages `_. This is a reliable site for binary packages for Python. You need to download the right package: The ``cpxx`` code refers to the Python version (e.g. ``cp27`` for Python 2.7); ``win32`` for 32bit Python installations and ``amd64`` for 64bit. diff -Nru mapproxy-1.11.0/doc/mapproxy_util_autoconfig.rst mapproxy-1.12.0/doc/mapproxy_util_autoconfig.rst --- mapproxy-1.11.0/doc/mapproxy_util_autoconfig.rst 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/doc/mapproxy_util_autoconfig.rst 2019-08-30 07:34:08.000000000 +0000 @@ -78,7 +78,7 @@ Overwrites ========== -It's likely that you need to tweak the created configuration – e.g. to define another coverage, disable featureinfo, etc. You can do this by editing the output file of course, or you can modify the output by defining all changes to an overwrite file. Overwrite files are applied everytime you call ``mapproxy-util autoconfig``. +It's likely that you need to tweak the created configuration – e.g. to define another coverage, disable featureinfo, etc. You can do this by editing the output file of course, or you can modify the output by defining all changes to an overwrite file. Overwrite files are applied every time you call ``mapproxy-util autoconfig``. Overwrites are YAML files that will be merged with the created configuration file. diff -Nru mapproxy-1.11.0/doc/seed.rst mapproxy-1.12.0/doc/seed.rst --- mapproxy-1.11.0/doc/seed.rst 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/doc/seed.rst 2019-08-30 07:34:08.000000000 +0000 @@ -1,13 +1,44 @@ Seeding ======= +Introduction +------------ + The MapProxy creates all tiles on demand. To improve the performance for commonly -requested views it is possible to pre-generate these tiles. The ``mapproxy-seed`` script does this task. +requested areas it is possible to pre-generate these tiles. The ``mapproxy-seed`` script does this task. + +The tool can seed one or more polygon or BBOX areas for each cache. It can seed missing tiles and refresh old tiles. A `cleanup` can be used to remove old tiles. + + +.. _seed_method: + +Method +~~~~~~ + +MapProxy does not seed the tile pyramid level by level, but traverses the tile pyramid depth-first. It starts in the first zoom level and decides if the tiles in the next zoom level need to be seeded by checking each subtile for intersection with the coverage. It recursively repeats this step for all tiles below till it reaches the last zoom level to seed. Only then, before getting back to the parent tile, the tile is actually seeded. + +The following shows in which order tiles are seeded for a simple cache with three levels:: + + Level 0 with 1 tile: + + 21 + + Level 1 with 4 tiles: + + 5 10 + 15 20 + + Level 2 with 16 tiles: + + 1 2 6 7 + 3 4 8 9 + 11 12 16 17 + 13 14 18 19 -The tool can seed one or more polygon or BBOX areas for each cached layer. -MapProxy does not seed the tile pyramid level by level, but traverses the tile pyramid depth-first, from bottom to top. This is optimized to work `with` the caches of your operating system and geospatial database, and not against. +This method is optimized to work `with` the caches of your operating system and geospatial database, as the same area is requested for multiple scales in direct succession. +It also makes checks against complex coverages efficient as subtiles can be rejected early on. mapproxy-seed ------------- @@ -42,6 +73,10 @@ Print a summary of all seeding and cleanup tasks and exit. +.. option:: --quiet + + Reduce the output of the progress logger. + .. option:: -i, --interactive Print a summary of each seeding and cleanup task and ask if ``mapproxy-seed`` should seed/cleanup that task. It will query for each task before it starts. @@ -70,7 +105,7 @@ .. option:: --duration Stop seeding process after this duration. This option accepts duration in the following format: 120s, 15m, 4h, 0.5d - Use this option in combination with ``--continue`` to be able to resume the seeding. By default, + Use this option in combination with ``--continue`` to be able to resume the seeding. Works only on Linux and Unix systems. .. option:: --reseed-file @@ -80,8 +115,7 @@ Only start seeding if ``--reseed-file`` is older then this duration. This option accepts duration in the following format: 120s, 15m, 4h, 0.5d - Use this option in combination with ``--continue`` to be able to resume the seeding. By default, - + Use this option in combination with ``--continue`` to be able to resume the seeding. By default, .. option:: --use-cache-lock @@ -125,6 +159,7 @@ --cleanup task3 + Configuration ------------- @@ -185,7 +220,7 @@ ``grids`` ~~~~~~~~~ A list with the grid names that should be seeded for the ``caches``. -The names should match the grid names in your mapproxy configuration. +The names should match the grid names in your MapProxy configuration. All caches of this tasks need to support the grids you specify here. By default, the grids that are common to all configured caches will be seeded. @@ -273,12 +308,12 @@ ``caches`` ~~~~~~~~~~ -A list with the caches where you want to cleanup old tiles. The names should match the cache names in your mapproxy configuration. +A list with the caches where you want to cleanup old tiles. The names should match the cache names in your MapProxy configuration. ``grids`` ~~~~~~~~~ A list with the grid names for the ``caches`` where you want to cleanup. -The names should match the grid names in your mapproxy configuration. +The names should match the grid names in your MapProxy configuration. All caches of this tasks need to support the grids you specify here. By default, the grids that are common to all configured caches will be used. @@ -401,26 +436,42 @@ srs: 'EPSG:4326' + +Output +------ + +``mapproxy-seed`` prints out the progress of the current seeding task on the console. + +Example progress log:: + + [16:48:26] 4 41.00% 582388, 4734701, 586740, 4737666 (5812 tiles) + + +The output starts with the current time and ends with the number of tiles it has seeded or removed so far. The third value is the current progress in percent. The progress can make large jumps, if the seeding detects that a tile and all its subtiles are outside of the seeding coverage. +The second and fourth value show the level and bounding box of where the seeding tool is in this moment. Keep in mind, that it does not seed level by level. This is described in :ref:`seeding method `. + + + .. _background_seeding: Example: Background seeding --------------------------- -.. versionadded:: 1.10.0 +.. versionadded:: 1.10.0 Works on Linux and Unix only The ``--duration`` option allows you run MapProxy seeding for a limited time. In combination with the ``--continue`` option, you can resume the seeding process at a later time. You can use this to call ``mapproxy-seed`` with ``cron`` to seed in the off-hours. -However, this will restart the seeding process from the begining everytime the is seeding completed. -You can prevent this with the ``--reeseed-interval`` and ``--reseed-file`` option. -The follwing example starts seeding for six hours. It will seed for another six hours, everytime you call this command again. Once all seed and cleanup tasks were proccessed the command will exit immediately everytime you call it within 14 days after the first call. After 14 days, the modification time of the ``reseed.time`` file will be updated and the re-seeding process starts again. +However, this will restart the seeding process from the beginning every time the is seeding completed. +You can prevent this with the ``--reeseed-interval`` and ``--reseed-file`` option. +The following example starts seeding for six hours. It will seed for another six hours, every time you call this command again. Once all seed and cleanup tasks were processed the command will exit immediately every time you call it within 14 days after the first call. After 14 days, the modification time of the ``reseed.time`` file will be updated and the re-seeding process starts again. :: mapproxy-seed -f mapproxy.yaml -s seed.yaml \ - --reseed-interval 14d --duration 6h --reseed-file reseed.time \ + --reseed-interval 14d --duration 6h --reseed-file reseed.time \ --continue --progress-file .mapproxy_seed_progress - + You can use the ``--reseed-file`` as a ``refresh_before`` and ``remove_before`` ``mtime``-file. diff -Nru mapproxy-1.11.0/doc/services.rst mapproxy-1.12.0/doc/services.rst --- mapproxy-1.11.0/doc/services.rst 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/doc/services.rst 2019-08-30 07:34:08.000000000 +0000 @@ -360,14 +360,14 @@ URL Template ~~~~~~~~~~~~ -WMTS RESTful services supports custom tile URLs. You can configure your own URL template with the ``restful_template`` option. +WMTS RESTful service supports custom URLs. You can configure your own URL templates with the ``restful_template`` and ``restful_fi_template``. -The default template is ``/{Layer}/{TileMatrixSet}/{TileMatrix}/{TileCol}/{TileRow}.{Format}`` +The default template is ``/{Layer}/{TileMatrixSet}/{TileMatrix}/{TileCol}/{TileRow}.{Format}`` for tile requests and ``/{Layer}/{TileMatrixSet}/{TileMatrix}/{TileCol}/{TileRow}/{I}/{J}.{InfoFormat}`` for feature info requests. All RESTful WMTS requests begin with with ``/wmts`` and this prefix must not be added to the template. -The template variables are identical with the WMTS specification. ``TileMatrixSet`` is the grid name, ``TileMatrix`` is the zoom level, ``TileCol`` and ``TileRow`` are the x and y of the tile. +The template variables are identical with the WMTS specification. ``TileMatrixSet`` is the grid name, ``TileMatrix`` is the zoom level, ``TileCol`` and ``TileRow`` are the x and y of the tile. ``InfoFormat`` is the suffix of the requested feature info format. ``I`` and ``J`` are pixel column and row of the requested feature. -You can access the tile x=3, y=9, z=4 at ``http://example.org//1.0.0/mylayer-mygrid/4-3-9/tile`` +You can access the tile x=3, y=9, z=4 at ``http://example.org/wmts/1.0.0/mylayer-mygrid/4-3-9/tile`` with the following configuration:: services: @@ -375,6 +375,38 @@ restful: true restful_template: '/1.0.0/{Layer}-{TileMatrixSet}/{TileMatrix}-{TileCol}-{TileRow}/tile' + restful_fi_template: + '/1.0.0/{Layer}-{TileMatrixSet}/{TileMatrix}-{TileCol}-{TileRow}/{I}-{J}/{InfoFormat}' + + +.. _wmts_feature_info: + +Feature Info +~~~~~~~~~~~~ + + +.. versionadded:: 1.12 + +``featureinfo_formats`` +""""""""""""""""""""""" + +A list of feature info formats that the WMTS service should offer. Each format requires the full mimetype and a filename suffix. The mimetype is the ``infoformat`` requested by KVP clients. The suffix is the ``{InfoFormat}`` requested by RESTful clients. + +No ``featureinfo_formats`` are configured by default. + +To enable GML and HTML feature info requests:: + + services: + wmts: + featureinfo_formats: + - mimetype: application/gml+xml; version=3.1 + suffix: gml + - mimetype: text/html + suffix: html + +You need to enable ``wms_opts.featureinfo`` for each queryable source. + +.. note:: The configuration differs from WMS as older WMS versions required a type like ``html`` instead of a mimetype like ``text/html``. .. index:: Demo Service, OpenLayers diff -Nru mapproxy-1.11.0/doc/sources.rst mapproxy-1.12.0/doc/sources.rst --- mapproxy-1.11.0/doc/sources.rst 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/doc/sources.rst 2019-08-30 07:34:08.000000000 +0000 @@ -80,17 +80,22 @@ ``featureinfo`` If this is set to ``true``, MapProxy will mark the layer as queryable and incoming `GetFeatureInfo` requests will be forwarded to the source server. +``featureinfo_format`` + The ``INFO_FORMAT`` for FeatureInfo requests. By default MapProxy will use the same format as requested by the client. + ``featureinfo_xslt`` Path to an XSLT script that should be used to transform incoming feature information. -``featureinfo_format`` - The ``INFO_FORMAT`` for FeatureInfo requests. By default MapProxy will use the same format as requested by the client. +``featureinfo_out_format`` + Output format returned by the XSLT script. By default MapProxy will use ``featureinfo_format``. - ``featureinfo_xslt`` and ``featureinfo_format`` +.. versionadded:: 1.12.0 + ``featureinfo_out_format`` See :ref:`FeatureInformation for more information `. + ``coverage`` ^^^^^^^^^^^^ @@ -215,6 +220,43 @@ You can either omit the ``layers`` in the ``req`` parameter, or you can use them to limit the tagged layers. In this case MapProxy will raise an error if you configure ``layers: lyr1,lyr2`` and then try to access ``wms:lyr2,lyr3`` for example. +``on_error`` +^^^^^^^^^^^^ + +.. versionadded:: 1.12.0 + +You can configure what MapProxy should do when the tile service returns an error. Instead of raising an error, MapProxy can generate a single color tile. You can configure if MapProxy should cache this tile, or if it should use it only to generate a tile or WMS response. + +You can configure multiple status codes within the ``on_error`` option. You can also use the catch-all value ``other``. This will not only catch all other HTTP status codes, but also source errors like HTTP timeouts or non-image responses. + +Each status code takes the following options: + +``response`` + + Specify the color of the tile that should be returned in case of this error. Can be either a list of color values (``[255, 255, 255]``, ``[255, 255, 255, 0]``)) or a hex string (``'#ffffff'``, ``'#fa1fbb00'``) with RGBA values, or the string ``transparent``. + +``cache`` + + Set this to ``True`` if MapProxy should cache the single color tile. Otherwise (``False``) MapProxy will use this generated tile only for this request. This is the default. + +You need to enable ``transparent`` for your source, if you use ``on_error`` responses with transparency. + +:: + + my_tile_source: + type: wms + req: + url: http://localhost:8080/service? + layers: base + on_error: + 500: + response: '#ede9e3' + cache: False + other: + response: '#ff0000' + cache: False + + Example configuration ^^^^^^^^^^^^^^^^^^^^^ @@ -308,6 +350,10 @@ layers: show: 0,1 url: http://example.org/ArcGIS/rest/services/Imagery/MapService transparent: true + on_error: + 500: + response: transparent + cache: True ImageServer example:: diff -Nru mapproxy-1.11.0/mapproxy/cache/geopackage.py mapproxy-1.12.0/mapproxy/cache/geopackage.py --- mapproxy-1.11.0/mapproxy/cache/geopackage.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/cache/geopackage.py 2019-08-30 07:34:08.000000000 +0000 @@ -481,7 +481,12 @@ return False def load_tile_metadata(self, tile): - self.load_tile(tile) + if not self.supports_timestamp: + # GPKG specification does not include tile timestamps. + # This sets the timestamp of the tile to epoch (1970s) + tile.timestamp = -1 + else: + self.load_tile(tile) class GeopackageLevelCache(TileCacheBase): @@ -507,7 +512,7 @@ geopackage_filename, self.tile_grid, self.table_name, - with_timestamps=True, + with_timestamps=False, timeout=self.timeout, wal=self.wal, ) diff -Nru mapproxy-1.11.0/mapproxy/cache/path.py mapproxy-1.12.0/mapproxy/cache/path.py --- mapproxy-1.11.0/mapproxy/cache/path.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/cache/path.py 2019-08-30 07:34:08.000000000 +0000 @@ -38,8 +38,8 @@ """ Return the path where all tiles for `level` will be stored. - >>> level_location(2, '/tmp/cache') - '/tmp/cache/02' + >>> os.path.abspath(level_location(2, '/tmp/cache')) == os.path.abspath('/tmp/cache/02') + True """ if isinstance(level, string_type): return os.path.join(cache_dir, level) diff -Nru mapproxy-1.11.0/mapproxy/cache/riak.py mapproxy-1.12.0/mapproxy/cache/riak.py --- mapproxy-1.11.0/mapproxy/cache/riak.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/cache/riak.py 2019-08-30 07:34:08.000000000 +0000 @@ -28,6 +28,10 @@ import riak except ImportError: riak = None +except TypeError: + import warnings + warnings.warn("riak version not compatible with this Python version") + riak = None import logging log = logging.getLogger(__name__) diff -Nru mapproxy-1.11.0/mapproxy/cache/s3.py mapproxy-1.12.0/mapproxy/cache/s3.py --- mapproxy-1.11.0/mapproxy/cache/s3.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/cache/s3.py 2019-08-30 07:34:08.000000000 +0000 @@ -22,7 +22,7 @@ from mapproxy.image import ImageSource from mapproxy.cache import path from mapproxy.cache.base import tile_buffer, TileCacheBase -from mapproxy.util import async +from mapproxy.util import async_ from mapproxy.util.py import reraise_exception try: @@ -50,11 +50,15 @@ class S3Cache(TileCacheBase): def __init__(self, base_path, file_ext, directory_layout='tms', - bucket_name='mapproxy', profile_name=None, - _concurrent_writer=4): + bucket_name='mapproxy', profile_name=None, region_name=None, endpoint_url=None, + _concurrent_writer=4, access_control_list=None): super(S3Cache, self).__init__() self.lock_cache_id = hashlib.md5(base_path.encode('utf-8') + bucket_name.encode('utf-8')).hexdigest() self.bucket_name = bucket_name + self.region_name = region_name + self.endpoint_url = endpoint_url + self.access_control_list = access_control_list + try: self.bucket = self.conn().head_bucket(Bucket=bucket_name) except botocore.exceptions.ClientError as e: @@ -82,7 +86,7 @@ raise ImportError("S3 Cache requires 'boto3' package.") try: - return s3_session().client("s3") + return s3_session().client("s3", region_name=self.region_name, endpoint_url=self.endpoint_url) except Exception as e: raise S3ConnectionError('Error during connection %s' % e) @@ -111,7 +115,7 @@ return True def load_tiles(self, tiles, with_metadata=True): - p = async.Pool(min(4, len(tiles))) + p = async_.Pool(min(4, len(tiles))) return all(p.map(self.load_tile, tiles)) def load_tile(self, tile, with_metadata=True): @@ -139,7 +143,7 @@ self.conn().delete_object(Bucket=self.bucket_name, Key=key) def store_tiles(self, tiles): - p = async.Pool(min(self._concurrent_writer, len(tiles))) + p = async_.Pool(min(self._concurrent_writer, len(tiles))) p.map(self.store_tile, tiles) def store_tile(self, tile): @@ -152,6 +156,8 @@ extra_args = {} if self.file_ext in ('jpeg', 'png'): extra_args['ContentType'] = 'image/' + self.file_ext + if self.access_control_list: + extra_args['ACL'] = self.access_control_list with tile_buffer(tile) as buf: self.conn().upload_fileobj( NopCloser(buf), # upload_fileobj closes buf, wrap in NopCloser diff -Nru mapproxy-1.11.0/mapproxy/cache/tile.py mapproxy-1.12.0/mapproxy/cache/tile.py --- mapproxy-1.11.0/mapproxy/cache/tile.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/cache/tile.py 2019-08-30 07:34:08.000000000 +0000 @@ -39,12 +39,15 @@ from functools import partial from contextlib import contextmanager from mapproxy.grid import MetaGrid +from mapproxy.image import BlankImageSource +from mapproxy.image.opts import ImageOptions from mapproxy.image.merge import merge_images -from mapproxy.image.tile import TileSplitter +from mapproxy.image.tile import TileSplitter, TiledImage from mapproxy.layer import MapQuery, BlankImage -from mapproxy.util import async +from mapproxy.util import async_ from mapproxy.util.py import reraise + class TileManager(object): """ Manages tiles for a single grid. @@ -56,9 +59,11 @@ return this or a new tile object. """ def __init__(self, grid, cache, sources, format, locker, image_opts=None, request_format=None, - meta_buffer=None, meta_size=None, minimize_meta_requests=False, identifier=None, - pre_store_filter=None, concurrent_tile_creators=1, tile_creator_class=None, - bulk_meta_tiles=False, + meta_buffer=None, meta_size=None, minimize_meta_requests=False, identifier=None, + pre_store_filter=None, concurrent_tile_creators=1, tile_creator_class=None, + bulk_meta_tiles=False, + rescale_tiles=0, + cache_rescaled_tiles=False, ): self.grid = grid self.cache = cache @@ -75,6 +80,9 @@ self.concurrent_tile_creators = concurrent_tile_creators self.tile_creator_class = tile_creator_class or TileCreator + self.rescale_tiles = rescale_tiles + self.cache_rescaled_tiles = cache_rescaled_tiles + if meta_buffer or (meta_size and not meta_size == [1, 1]): if all(source.supports_meta_tiles for source in sources): self.meta_grid = MetaGrid(grid, meta_size=meta_size, meta_buffer=meta_buffer) @@ -104,23 +112,52 @@ self.cache.cleanup() def load_tile_coord(self, tile_coord, dimensions=None, with_metadata=False): - tile = Tile(tile_coord) - self.cache.load_tile(tile, with_metadata) + return self.load_tile_coords( + [tile_coord], dimensions=dimensions, with_metadata=with_metadata, + )[0] - if tile.coord is not None and not self.is_cached(tile, dimensions=dimensions): - # missing or staled - creator = self.creator(dimensions=dimensions) - created_tiles = creator.create_tiles([tile]) - for created_tile in created_tiles: - if created_tile.coord == tile_coord: - return created_tile - - return tile def load_tile_coords(self, tile_coords, dimensions=None, with_metadata=False): tiles = TileCollection(tile_coords) + rescale_till_zoom = 0 + if self.rescale_tiles: + rescaled_tiles = {} + + for t in tiles.tiles: + # Use zoom level from first None tile. + if t.coord is not None: + rescale_till_zoom = t.coord[2] + self.rescale_tiles + break + else: + return tiles + + if rescale_till_zoom < 0: + rescale_till_zoom = 0 + if rescale_till_zoom > self.grid.levels: + rescale_till_zoom = self.grid.levels + + tiles = self._load_tile_coords( + tiles, dimensions=dimensions, with_metadata=with_metadata, + rescale_till_zoom=rescale_till_zoom, rescaled_tiles={}, + ) + + for t in tiles.tiles: + # Remove our internal marker source, for missing tiles. + if t.source is RESCALE_TILE_MISSING: + t.source = None + + return tiles + + def _load_tile_coords(self, tiles, dimensions=None, with_metadata=False, + rescale_till_zoom=None, rescaled_tiles=None, + ): uncached_tiles = [] + if rescaled_tiles: + for t in tiles: + if t.coord in rescaled_tiles: + t.source = rescaled_tiles[t.coord].source + # load all in batch self.cache.load_tiles(tiles, with_metadata) @@ -132,6 +169,9 @@ if uncached_tiles: creator = self.creator(dimensions=dimensions) created_tiles = creator.create_tiles(uncached_tiles) + if not created_tiles and self.rescale_tiles: + created_tiles = [self._scaled_tile(t, rescale_till_zoom, rescaled_tiles) for t in uncached_tiles] + for created_tile in created_tiles: if created_tile.coord in tiles: tiles[created_tile.coord].source = created_tile.source @@ -202,6 +242,65 @@ tile = img_filter(tile) return tile + def _scaled_tile(self, tile, stop_zoom, rescaled_tiles): + """ + Try to load tile by loading, scaling and clipping tiles from zoom levels above or + below. stop_zoom determines if tiles from above should be scaled up, or if tiles + from below should be scaled down. + Returns an empty Tile if tile zoom level is stop_zoom. + """ + if tile.coord in rescaled_tiles: + return rescaled_tiles[tile.coord] + + # Cache tile in rescaled_tiles. We initially set source to a fixed + # BlankImageSource and overwrite it if we actually rescaled the tile. + tile.source = RESCALE_TILE_MISSING + rescaled_tiles[tile.coord] = tile + + tile_bbox = self.grid.tile_bbox(tile.coord) + current_zoom = tile.coord[2] + if stop_zoom == current_zoom: + return tile + if stop_zoom > current_zoom: + src_level = current_zoom + 1 + else: + src_level = current_zoom - 1 + + src_bbox, src_tile_grid, affected_tile_coords = self.grid.get_affected_level_tiles(tile_bbox, src_level) + + affected_tiles = TileCollection(affected_tile_coords) + for t in affected_tiles: + # Add sources of cached tiles, to avoid loading same tile multiple times + # loading recursive. + if t.coord in rescaled_tiles: + t.source = rescaled_tiles[t.coord].source + + tile_collection = self._load_tile_coords( + affected_tiles, + rescale_till_zoom=stop_zoom, + rescaled_tiles=rescaled_tiles, + ) + + if tile_collection.blank: + return tile + + tile_sources = [] + for t in tile_collection: + # Replace RESCALE_TILE_MISSING with None, before transforming tiles. + tile_sources.append(t.source if t.source is not RESCALE_TILE_MISSING else None) + + tiled_image = TiledImage(tile_sources, src_bbox=src_bbox, src_srs=self.grid.srs, + tile_grid=src_tile_grid, tile_size=self.grid.tile_size) + tile.source = tiled_image.transform(tile_bbox, self.grid.srs, self.grid.tile_size, self.image_opts) + + if self.cache_rescaled_tiles: + self.cache.store_tile(tile) + return tile + +# RESCALE_TILE_MISSING is a dummy source to prevent a tile cache from loading +# a tile that we already found out is missing. +RESCALE_TILE_MISSING = BlankImageSource((256, 256), ImageOptions()) + class TileCreator(object): def __init__(self, tile_mgr, dimensions=None, image_merger=None, bulk_meta_tiles=False): self.cache = tile_mgr.cache @@ -220,6 +319,8 @@ return self.tile_mgr.is_cached(tile) def create_tiles(self, tiles): + if not self.sources: + return [] if not self.meta_grid: created_tiles = self._create_single_tiles(tiles) elif self.tile_mgr.minimize_meta_requests and len(tiles) > 1: @@ -250,7 +351,7 @@ def _create_threaded(self, create_func, tiles): result = [] - async_pool = async.Pool(self.tile_mgr.concurrent_tile_creators) + async_pool = async_.Pool(self.tile_mgr.concurrent_tile_creators) for new_tiles in async_pool.imap(create_func, tiles): result.extend(new_tiles) return result @@ -303,7 +404,7 @@ return (img, source.coverage) layers = [] - for layer in async.imap(get_map_from_source, self.sources): + for layer in async_.imap(get_map_from_source, self.sources): if layer[0] is not None: layers.append(layer) @@ -358,7 +459,7 @@ main_tile = Tile(meta_tile.main_tile_coord) with self.tile_mgr.lock(main_tile): if not all(self.is_cached(t) for t in meta_tile.tiles if t is not None): - async_pool = async.Pool(self.tile_mgr.concurrent_tile_creators) + async_pool = async_.Pool(self.tile_mgr.concurrent_tile_creators) def query_tile(coord): try: query = MapQuery(self.grid.tile_bbox(coord), tile_size, self.grid.srs, self.tile_mgr.request_format, @@ -538,6 +639,13 @@ """ return all((t.source is None for t in self.tiles)) + @property + def blank(self): + """ + Returns True if all sources collection are BlankImageSources or have not source at all. + """ + return all((t.source is None or isinstance(t.source, BlankImageSource) for t in self.tiles)) + def __repr__(self): return 'TileCollection(%r)' % self.tiles diff -Nru mapproxy-1.11.0/mapproxy/client/cgi.py mapproxy-1.12.0/mapproxy/client/cgi.py --- mapproxy-1.11.0/mapproxy/client/cgi.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/client/cgi.py 2019-08-30 07:34:08.000000000 +0000 @@ -20,17 +20,16 @@ import errno import os import re +import subprocess import time from mapproxy.source import SourceError from mapproxy.image import ImageSource from mapproxy.client.http import HTTPClientError from mapproxy.client.log import log_request -from mapproxy.util.async import import_module from mapproxy.compat.modules import urlparse from mapproxy.compat import BytesIO -subprocess = import_module('subprocess') def split_cgi_response(data): headers = [] diff -Nru mapproxy-1.11.0/mapproxy/client/http.py mapproxy-1.12.0/mapproxy/client/http.py --- mapproxy-1.11.0/mapproxy/client/http.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/client/http.py 2019-08-30 07:34:08.000000000 +0000 @@ -251,11 +251,10 @@ return url, (username, password) -_http_client = HTTPClient() def open_url(url): - return _http_client.open(url) - -retrieve_url = open_url + url, (username, password) = auth_data_from_url(url) + http_client = HTTPClient(url, username, password) + return http_client.open(url) def retrieve_image(url, client=None): """ diff -Nru mapproxy-1.11.0/mapproxy/client/tile.py mapproxy-1.12.0/mapproxy/client/tile.py --- mapproxy-1.11.0/mapproxy/client/tile.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/client/tile.py 2019-08-30 07:34:08.000000000 +0000 @@ -1,12 +1,12 @@ # This file is part of the MapProxy project. # Copyright (C) 2010 Omniscale -# +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,36 +15,19 @@ from mapproxy.client.http import retrieve_image -class TMSClient(object): - def __init__(self, url, format='png', http_client=None): - self.url = url - self.http_client = http_client - self.format = format - - def get_tile(self, tile_coord, format=None): - x, y, z = tile_coord - url = '%s/%d/%d/%d.%s' % (self.url, z, x, y, format or self.format) - if self.http_client: - return self.http_client.open_image(url) - else: - return retrieve_image(url) - - def __repr__(self): - return '%s(%r, %r)' % (self.__class__.__name__, self.url, self.format) - class TileClient(object): def __init__(self, url_template, http_client=None, grid=None): self.url_template = url_template self.http_client = http_client self.grid = grid - + def get_tile(self, tile_coord, format=None): url = self.url_template.substitute(tile_coord, format, self.grid) if self.http_client: return self.http_client.open_image(url) else: return retrieve_image(url) - + def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.url_template) @@ -61,15 +44,15 @@ >>> t = TileURLTemplate('http://foo/tiles/%(tc_path)s.png') >>> t.substitute((7, 4, 3)) 'http://foo/tiles/03/000/000/007/000/000/004.png' - + >>> t = TileURLTemplate('http://foo/tms/1.0.0/%(tms_path)s.%(format)s') >>> t.substitute((7, 4, 3)) 'http://foo/tms/1.0.0/3/7/4.png' - + >>> t = TileURLTemplate('http://foo/tms/1.0.0/lyr/%(tms_path)s.%(format)s') >>> t.substitute((7, 4, 3), 'jpeg') 'http://foo/tms/1.0.0/lyr/3/7/4.jpeg' - + """ def __init__(self, template, format='png'): self.template= template @@ -96,7 +79,7 @@ data['bbox'] = bbox(tile_coord, grid) return self.template % data - + def __repr__(self): return '%s(%r, format=%r)' % ( self.__class__.__name__, self.template, self.format) @@ -159,7 +142,7 @@ '0.00000000,-15.00000000,10.00000000,-5.00000000' >>> bbox((0, 0, 1), grid) '0.00000000,-15.00000000,5.00000000,-10.00000000' - + >>> grid = tile_grid(4326, bbox=(0, -15, 10, -5), origin='nw') >>> bbox((0, 0, 1), grid) '0.00000000,-10.00000000,5.00000000,-5.00000000' diff -Nru mapproxy-1.11.0/mapproxy/client/wms.py mapproxy-1.12.0/mapproxy/client/wms.py --- mapproxy-1.11.0/mapproxy/client/wms.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/client/wms.py 2019-08-30 07:34:08.000000000 +0000 @@ -21,7 +21,7 @@ from mapproxy.layer import InfoQuery from mapproxy.source import SourceError from mapproxy.client.http import HTTPClient -from mapproxy.srs import make_lin_transf, SRS +from mapproxy.srs import make_lin_transf, SRS, SupportedSRS from mapproxy.image import ImageSource from mapproxy.image.opts import ImageOptions from mapproxy.featureinfo import create_featureinfo_doc @@ -115,15 +115,21 @@ self.request_template = request_template self.http_client = http_client or HTTPClient() if not supported_srs and self.request_template.params.srs is not None: - supported_srs = [SRS(self.request_template.params.srs)] - self.supported_srs = supported_srs or [] + supported_srs = SupportedSRS([SRS(self.request_template.params.srs)]) + self.supported_srs = supported_srs def get_info(self, query): if self.supported_srs and query.srs not in self.supported_srs: query = self._get_transformed_query(query) resp = self._retrieve(query) - info_format = resp.headers.get('Content-type', None) + + # use from template if available + info_format = self.request_template.params.get('info_format') + if not info_format: + # otherwise from response + info_format = resp.headers.get('Content-type', None) if not info_format: + # otherwise from query info_format = query.info_format return create_featureinfo_doc(resp.read(), info_format) @@ -135,7 +141,7 @@ req_bbox = query.bbox req_coord = make_lin_transf((0, 0, query.size[0], query.size[1]), req_bbox)(query.pos) - info_srs = self._best_supported_srs(req_srs) + info_srs = self.supported_srs.best_srs(req_srs) info_bbox = req_srs.transform_bbox_to(info_srs, req_bbox) # calculate new info_size to keep square pixels after transform_bbox_to info_aratio = (info_bbox[3] - info_bbox[1])/(info_bbox[2] - info_bbox[0]) @@ -155,10 +161,6 @@ ) return info_query - def _best_supported_srs(self, srs): - # always choose the first, distortion should not matter - return self.supported_srs[0] - def _retrieve(self, query): url = self._query_url(query) return self.http_client.open(url) diff -Nru mapproxy-1.11.0/mapproxy/compat/image.py mapproxy-1.12.0/mapproxy/compat/image.py --- mapproxy-1.11.0/mapproxy/compat/image.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/compat/image.py 2019-08-30 07:34:08.000000000 +0000 @@ -21,8 +21,10 @@ try: import PIL from PIL import Image, ImageColor, ImageDraw, ImageFont, ImagePalette, ImageChops, ImageMath + from PIL.TiffImagePlugin import ImageFileDirectory_v2, TiffTags # prevent pyflakes warnings Image, ImageColor, ImageDraw, ImageFont, ImagePalette, ImageChops, ImageMath + ImageFileDirectory_v2, TiffTags except ImportError: # allow MapProxy to start without PIL (for tilecache only). # issue warning and raise ImportError on first use of @@ -70,4 +72,4 @@ img = img.convert('RGB').convert('P', palette=Image.ADAPTIVE, colors=colors) return img -quantize = quantize_pil \ No newline at end of file +quantize = quantize_pil diff -Nru mapproxy-1.11.0/mapproxy/config/config.py mapproxy-1.12.0/mapproxy/config/config.py --- mapproxy-1.11.0/mapproxy/config/config.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/config/config.py 2019-08-30 07:34:08.000000000 +0000 @@ -75,10 +75,8 @@ config = _config.top if config is None: import warnings - import sys - if 'nosetests' not in sys.argv[0]: - warnings.warn("calling un-configured base_config", - DeprecationWarning, stacklevel=2) + warnings.warn("calling un-configured base_config", + DeprecationWarning, stacklevel=2) config = load_default_config() config.conf_base_dir = os.getcwd() finish_base_config(config) diff -Nru mapproxy-1.11.0/mapproxy/config/loader.py mapproxy-1.12.0/mapproxy/config/loader.py --- mapproxy-1.11.0/mapproxy/config/loader.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/config/loader.py 2019-08-30 07:34:08.000000000 +0000 @@ -318,6 +318,22 @@ return grid +def preferred_srs(conf): + from mapproxy.srs import SRS, PreferredSrcSRS + + preferred_conf = conf.get('preferred_src_proj', {}) + + if not preferred_conf: + return + + preferred = PreferredSrcSRS() + + for target, preferred_srcs in preferred_conf.items(): + preferred.add(SRS(target), [SRS(s) for s in preferred_srcs]) + + return preferred + + class GlobalConfiguration(ConfigurationBase): def __init__(self, conf_base_dir, conf, context): ConfigurationBase.__init__(self, conf, context) @@ -327,6 +343,7 @@ finish_base_config(self.base_config) self.image_options = ImageOptionsConfiguration(self.conf.get('image', {}), context) + self.preferred_srs = preferred_srs(self.conf.get('srs', {})) self.renderd_address = self.get_value('renderd.address') def _copy_conf_values(self, d, target): @@ -395,6 +412,11 @@ jpeg_quality = options.pop('jpeg_quality', None) if jpeg_quality and not isinstance(jpeg_quality, int): raise ConfigurationError('jpeg_quality is not an integer') + + tiff_compression = options.pop('tiff_compression', None) + if tiff_compression and tiff_compression not in ('raw', 'tiff_lzw', 'jpeg'): + raise ConfigurationError('unknown tiff_compression') + quantizer = options.pop('quantizer', None) if quantizer and quantizer not in ('fastoctree', 'mediancut'): raise ConfigurationError('unknown quantizer') @@ -549,6 +571,14 @@ self.conf.setdefault('image', {})['transparent'] = self.conf['transparent'] return self.context.globals.image_options.image_opts(self.conf.get('image', {}), format) + def supported_srs(self): + from mapproxy.srs import SRS, SupportedSRS + + supported_srs = [SRS(code) for code in self.conf.get('supported_srs', [])] + if not supported_srs: + return None + return SupportedSRS(supported_srs, self.context.globals.preferred_srs) + def http_client(self, url): from mapproxy.client.http import auth_data_from_url, HTTPClient @@ -614,8 +644,6 @@ from mapproxy.source import DummySource return DummySource(coverage=self.coverage()) - # Get the supported SRS codes and formats from the configuration. - supported_srs = [SRS(code) for code in self.conf.get("supported_srs", [])] supported_formats = [file_ext(f) for f in self.conf.get("supported_formats", [])] # Construct the parameters @@ -635,10 +663,11 @@ image_opts = self.image_opts(format=params.get('format')) return ArcGISSource(client, image_opts=image_opts, coverage=coverage, res_range=res_range, - supported_srs=supported_srs, - supported_formats=supported_formats or None) - + supported_srs=self.supported_srs(), + supported_formats=supported_formats or None, + error_handler=self.on_error_handler()) + @memoize def fi_source(self, params=None): from mapproxy.client.arcgis import ArcGISInfoClient from mapproxy.request.arcgis import create_identify_request @@ -649,7 +678,6 @@ request_format = self.conf['req'].get('format') if request_format: params['format'] = request_format - supported_srs = [SRS(code) for code in self.conf.get('supported_srs', [])] fi_source = None if self.conf.get('opts', {}).get('featureinfo', False): opts = self.conf['opts'] @@ -661,7 +689,7 @@ http_client, fi_request.url = self.http_client(fi_request.url) fi_client = ArcGISInfoClient(fi_request, - supported_srs=supported_srs, + supported_srs=self.supported_srs(), http_client=http_client, tolerance=tolerance, return_geometries=return_geometries, @@ -696,7 +724,10 @@ if not has_xslt_support: raise ValueError('featureinfo_xslt requires lxml. Please install.') fi_xslt = context.globals.abspath(fi_xslt) - fi_transformer = XSLTransformer(fi_xslt) + fi_format = conf.get('featureinfo_out_format') + if not fi_format: + fi_format = conf.get('featureinfo_format') + fi_transformer = XSLTransformer(fi_xslt, fi_format) return fi_transformer def image_opts(self, format=None): @@ -711,7 +742,7 @@ from mapproxy.client.wms import WMSClient from mapproxy.request.wms import create_request from mapproxy.source.wms import WMSSource - from mapproxy.srs import SRS + from mapproxy.srs import SRS, SupportedSRS if not self.conf.get('wms_opts', {}).get('map', True): return None @@ -728,7 +759,6 @@ image_opts = self.image_opts(format=params.get('format')) - supported_srs = [SRS(code) for code in self.conf.get('supported_srs', [])] supported_formats = [file_ext(f) for f in self.conf.get('supported_formats', [])] version = self.conf.get('wms_opts', {}).get('version', '1.1.1') @@ -766,9 +796,10 @@ return WMSSource(client, image_opts=image_opts, coverage=coverage, res_range=res_range, transparent_color=transparent_color, transparent_color_tolerance=transparent_color_tolerance, - supported_srs=supported_srs, + supported_srs=self.supported_srs(), supported_formats=supported_formats or None, - fwd_req_params=fwd_req_params) + fwd_req_params=fwd_req_params, + error_handler=self.on_error_handler()) def fi_source(self, params=None): from mapproxy.client.wms import WMSInfoClient @@ -780,7 +811,6 @@ request_format = self.conf['req'].get('format') if request_format: params['format'] = request_format - supported_srs = [SRS(code) for code in self.conf.get('supported_srs', [])] fi_source = None if self.conf.get('wms_opts', {}).get('featureinfo', False): wms_opts = self.conf['wms_opts'] @@ -795,9 +825,11 @@ self.context) http_client, fi_request.url = self.http_client(fi_request.url) - fi_client = WMSInfoClient(fi_request, supported_srs=supported_srs, + fi_client = WMSInfoClient(fi_request, supported_srs=self.supported_srs(), http_client=http_client) - fi_source = WMSInfoSource(fi_client, fi_transformer=fi_transformer) + coverage = self.coverage() + fi_source = WMSInfoSource(fi_client, fi_transformer=fi_transformer, + coverage=coverage) return fi_source def lg_source(self, params=None): @@ -1109,6 +1141,15 @@ profile_name = self.context.globals.get_value('cache.profile_name', self.conf, global_key='cache.s3.profile_name') + region_name = self.context.globals.get_value('cache.region_name', self.conf, + global_key='cache.s3.region_name') + + endpoint_url = self.context.globals.get_value('cache.endpoint_url', self.conf, + global_key='cache.s3.endpoint_url') + + access_control_list = self.context.globals.get_value('cache.access_control_list', self.conf, + global_key='cache.s3.access_control_list') + directory_layout = self.conf['cache'].get('directory_layout', 'tms') base_path = self.conf['cache'].get('directory', None) @@ -1121,6 +1162,9 @@ directory_layout=directory_layout, bucket_name=bucket_name, profile_name=profile_name, + region_name=region_name, + endpoint_url=endpoint_url, + access_control_list=access_control_list ) def _sqlite_cache(self, grid_conf, file_ext): @@ -1428,6 +1472,22 @@ concurrent_tile_creators = self.context.globals.get_value('concurrent_tile_creators', self.conf, global_key='cache.concurrent_tile_creators') + cache_rescaled_tiles = self.conf.get('cache_rescaled_tiles') + upscale_tiles = self.conf.get('upscale_tiles', 0) + if upscale_tiles < 0: + raise ConfigurationError("upscale_tiles must be positive") + downscale_tiles = self.conf.get('downscale_tiles', 0) + if downscale_tiles < 0: + raise ConfigurationError("downscale_tiles must be positive") + if upscale_tiles and downscale_tiles: + raise ConfigurationError("cannot use both upscale_tiles and downscale_tiles") + + rescale_tiles = 0 + if upscale_tiles: + rescale_tiles = -upscale_tiles + if downscale_tiles: + rescale_tiles = downscale_tiles + renderd_address = self.context.globals.get_value('renderd.address', self.conf) band_merger = None @@ -1508,6 +1568,8 @@ pre_store_filter=tile_filter, tile_creator_class=tile_creator_class, bulk_meta_tiles=bulk_meta_tiles, + cache_rescaled_tiles=cache_rescaled_tiles, + rescale_tiles=rescale_tiles, ) extent = merge_layer_extents(sources) if extent.is_default: @@ -1538,12 +1600,13 @@ main_grid = grid caches.append((CacheMapLayer(tile_manager, extent=extent, image_opts=image_opts, max_tile_limit=max_tile_limit), - (grid.srs,))) + grid.srs)) if len(caches) == 1: layer = caches[0][0] else: - layer = SRSConditional(caches, caches[0][0].extent, opacity=image_opts.opacity) + layer = SRSConditional(caches, caches[0][0].extent, opacity=image_opts.opacity, + preferred_srs=self.context.globals.preferred_srs) if 'use_direct_from_level' in self.conf: self.conf['use_direct_from_res'] = main_grid.resolution(self.conf['use_direct_from_level']) @@ -1670,6 +1733,7 @@ from mapproxy.cache.dummy import DummyCache sources = [] + fi_only_sources = [] if 'tile_sources' in self.conf: sources = self.conf['tile_sources'] else: @@ -1683,18 +1747,31 @@ continue # and WMS layers with map: False (i.e. FeatureInfo only sources) if src_conf['type'] == 'wms' and src_conf.get('wms_opts', {}).get('map', True) == False: + fi_only_sources.append(source_name) continue return [] sources.append(source_name) if len(sources) > 1: + # skip layers with more then one source return [] + dimensions = self.dimensions() tile_layers = [] for cache_name in sources: + fi_sources = [] + fi_source_names = cache_source_names(self.context, cache_name) + + for fi_source_name in fi_source_names + fi_only_sources: + if fi_source_name not in self.context.sources: continue + if not hasattr(self.context.sources[fi_source_name], 'fi_source'): continue + fi_source = self.context.sources[fi_source_name].fi_source() + if fi_source: + fi_sources.append(fi_source) + for grid, extent, cache_source in self.context.caches[cache_name].caches(): if dimensions and not isinstance(cache_source.cache, DummyCache): # caching of dimension layers is not supported yet @@ -1715,8 +1792,15 @@ md['format'] = self.context.caches[cache_name].image_opts().format md['cache_name'] = cache_name md['extent'] = extent - tile_layers.append(TileLayer(self.conf['name'], self.conf['title'], - md, cache_source, dimensions=dimensions)) + tile_layers.append( + TileLayer( + self.conf['name'], self.conf['title'], + info_sources=fi_sources, + md=md, + tile_manager=cache_source, + dimensions=dimensions, + ) + ) return tile_layers @@ -1832,19 +1916,35 @@ max_tile_age = self.context.globals.get_value('tiles.expires_hours') max_tile_age *= 60 * 60 # seconds + info_formats = conf.get('featureinfo_formats', []) + info_formats = odict((f['suffix'], f['mimetype']) for f in info_formats) + if kvp is None and restful is None: kvp = restful = True services = [] if kvp: - services.append(WMTSServer(layers, md, max_tile_age=max_tile_age)) + services.append( + WMTSServer( + layers, md, max_tile_age=max_tile_age, + info_formats=info_formats, + ) + ) + if restful: template = conf.get('restful_template') + fi_template = conf.get('restful_featureinfo_template') if template and '{{' in template: # TODO remove warning in 1.6 log.warn("double braces in WMTS restful_template are deprecated {{x}} -> {x}") - services.append(WMTSRestServer(layers, md, template=template, - max_tile_age=max_tile_age)) + services.append( + WMTSRestServer( + layers, md, template=template, + fi_template=fi_template, + max_tile_age=max_tile_age, + info_formats=info_formats, + ) + ) return services @@ -1884,10 +1984,6 @@ versions = conf.get('versions') if versions: - versions = sorted([Version(v) for v in versions]) - - versions = conf.get('versions') - if versions: versions = sorted([Version(v) for v in versions]) max_output_pixels = self.context.globals.get_value('max_output_pixels', conf, diff -Nru mapproxy-1.11.0/mapproxy/config/spec.py mapproxy-1.12.0/mapproxy/config/spec.py --- mapproxy-1.11.0/mapproxy/config/spec.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/config/spec.py 2019-08-30 07:34:08.000000000 +0000 @@ -143,6 +143,9 @@ 'directory_layout': str(), 'directory': str(), 'profile_name': str(), + 'region_name': str(), + 'endpoint_url': str(), + 'access_control_list': str(), 'tile_lock_dir': str(), }, 'riak': { @@ -369,6 +372,8 @@ 's3': { 'bucket_name': str(), 'profile_name': str(), + 'region_name': str(), + 'endpoint_url': str(), }, }, 'grid': { @@ -378,6 +383,7 @@ 'axis_order_ne': [str()], 'axis_order_en': [str()], 'proj_data_dir': str(), + 'preferred_src_proj': {anything(): [str()]}, }, 'tiles': { 'expires_hours': number(), @@ -408,6 +414,9 @@ 'use_direct_from_level': number(), 'use_direct_from_res': number(), 'link_single_color_images': bool(), + 'cache_rescaled_tiles': bool(), + 'upscale_tiles': int(), + 'downscale_tiles': int(), 'watermark': { 'text': string_type, 'font_size': number(), @@ -431,7 +440,14 @@ 'kvp': bool(), 'restful': bool(), 'restful_template': str(), + 'restful_featureinfo_template': str(), 'md': ogc_service_md, + 'featureinfo_formats': [ + { + required('mimetype'): str(), + 'suffix': str(), + }, + ], }, 'wms': { 'srs': [str()], @@ -464,6 +480,7 @@ 'legendurl': str(), 'featureinfo_format': str(), 'featureinfo_xslt': str(), + 'featureinfo_out_format': str(), }, 'image': combined(image_opts, { 'opacity':number(), @@ -473,6 +490,7 @@ 'supported_formats': [str()], 'supported_srs': [str()], 'http': http_opts, + 'on_error': on_error, 'forward_req_params': [str()], required('req'): { required('url'): str(), @@ -535,7 +553,8 @@ 'featureinfo_return_geometries': bool(), }, 'supported_srs': [str()], - 'http': http_opts + 'http': http_opts, + 'on_error': on_error }), 'debug': { }, @@ -572,13 +591,3 @@ 'parts': anything(), } -if __name__ == '__main__': - import sys - import yaml - for f in sys.argv[1:]: - data = yaml.load(open(f)) - try: - validate(mapproxy_yaml_spec, data) - except ValidationError as ex: - for err in ex.errors: - print('%s: %s' % (f, err)) diff -Nru mapproxy-1.11.0/mapproxy/config_template/base_config/full_example.yaml mapproxy-1.12.0/mapproxy/config_template/base_config/full_example.yaml --- mapproxy-1.11.0/mapproxy/config_template/base_config/full_example.yaml 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/config_template/base_config/full_example.yaml 2019-08-30 07:34:08.000000000 +0000 @@ -351,7 +351,7 @@ osm_wms: type: wms req: - url: http://osm.omniscale.net/proxy/service? + url: https://maps.omniscale.net/v2/demo/style.default/service? layers: osm # WMS source for use with tagged sources diff -Nru mapproxy-1.11.0/mapproxy/config_template/base_config/mapproxy.yaml mapproxy-1.12.0/mapproxy/config_template/base_config/mapproxy.yaml --- mapproxy-1.11.0/mapproxy/config_template/base_config/mapproxy.yaml 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/config_template/base_config/mapproxy.yaml 2019-08-30 07:34:08.000000000 +0000 @@ -50,8 +50,7 @@ osm_wms: type: wms req: - # use of this source is only permitted for testing - url: http://osm.omniscale.net/proxy/service? + url: https://maps.omniscale.net/v2/demo/style.default/service? layers: osm grids: diff -Nru mapproxy-1.11.0/mapproxy/config_template/__init__.py mapproxy-1.12.0/mapproxy/config_template/__init__.py --- mapproxy-1.11.0/mapproxy/config_template/__init__.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/config_template/__init__.py 2019-08-30 07:34:08.000000000 +0000 @@ -1,14 +0,0 @@ -try: - from paste.util.template import paste_script_template_renderer - from paste.script.templates import Template #, var - - class PasterConfigurationTemplate(Template): - _template_dir = 'paster' - summary = "MapProxy configuration template" - vars = [ - # var('varname', 'help text', default='value'), - ] - - template_renderer = staticmethod(paste_script_template_renderer) -except ImportError: - pass \ No newline at end of file diff -Nru mapproxy-1.11.0/mapproxy/config_template/paster/etc/config.ini mapproxy-1.12.0/mapproxy/config_template/paster/etc/config.ini --- mapproxy-1.11.0/mapproxy/config_template/paster/etc/config.ini 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/config_template/paster/etc/config.ini 1970-01-01 00:00:00.000000000 +0000 @@ -1,21 +0,0 @@ -[app:main] -use = egg:MapProxy#app -mapproxy_conf = %(here)s/mapproxy.yaml -log_conf = %(here)s/log_deploy.ini - -[server:main] -use = egg:Flup#fcgi_fork -## connect via socket -socket = %(here)s/../var/fcgi-socket -# webserver runs as other user -umask = 000 -# webserver runs in same group/user -# umask = 002 -maxRequests = 500 -minSpare = 4 -maxSpare = 16 -maxChildren = 64 - -## connect via tcp/ip -# host = 127.0.0.1 -# port = 5050 diff -Nru mapproxy-1.11.0/mapproxy/config_template/paster/etc/config.wsgi mapproxy-1.12.0/mapproxy/config_template/paster/etc/config.wsgi --- mapproxy-1.11.0/mapproxy/config_template/paster/etc/config.wsgi 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/config_template/paster/etc/config.wsgi 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -# WSGI module for use with Apache mod_wsgi - -import os -from paste.deploy import loadapp - -application = loadapp('config:config.ini', relative_to=os.path.dirname(__file__)) \ No newline at end of file diff -Nru mapproxy-1.11.0/mapproxy/config_template/paster/etc/develop.ini mapproxy-1.12.0/mapproxy/config_template/paster/etc/develop.ini --- mapproxy-1.11.0/mapproxy/config_template/paster/etc/develop.ini 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/config_template/paster/etc/develop.ini 1970-01-01 00:00:00.000000000 +0000 @@ -1,39 +0,0 @@ -[app:main] -use = egg:MapProxy#app -mapproxy_conf = %(here)s/mapproxy.yaml -log_conf = -reload_files = %(here)s/*.* -filter-with = translogger - -[server:main] -## connect via tcp/ip -use = egg:Paste#http -host = 0.0.0.0 -port = 8080 - -[filter:translogger] -use = egg:Paste#translogger - -# logging configuration - -[loggers] -keys=root - -[handlers] -keys=console - -[formatters] -keys=default - -[logger_root] -level=INFO -qualname=root -handlers=console - -[handler_console] -class=StreamHandler -formatter=default -args=(sys.stdout, ) - -[formatter_default] -format=%(asctime)s - %(levelname)s - %(process)d:%(name)s:%(funcName)s - %(message)s diff -Nru mapproxy-1.11.0/mapproxy/config_template/paster/etc/log_deploy.ini mapproxy-1.12.0/mapproxy/config_template/paster/etc/log_deploy.ini --- mapproxy-1.11.0/mapproxy/config_template/paster/etc/log_deploy.ini 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/config_template/paster/etc/log_deploy.ini 1970-01-01 00:00:00.000000000 +0000 @@ -1,45 +0,0 @@ -[loggers] -keys=root,mapproxy,client - -[handlers] -keys=console,mapproxy,client - -[formatters] -keys=default,client - -[logger_root] -level=WARN -qualname=root -handlers=console - -[logger_mapproxy] -level=INFO -qualname=mapproxy -handlers=mapproxy - -[logger_client] -level=INFO -qualname=mapproxy.client.http -propagate=0 -handlers=client - -[handler_console] -class=StreamHandler -formatter=default -args=(sys.stdout, ) - -[handler_mapproxy] -class=FileHandler -formatter=default -args=(r"%(here)s/../var/proxy.log", "a") - -[handler_client] -class=FileHandler -formatter=client -args=(r"%(here)s/../var/client.log", "a") - -[formatter_default] -format=%(asctime)s - %(levelname)s - %(process)d:%(name)s:%(funcName)s - %(message)s - -[formatter_client] -format=%(message)s diff -Nru mapproxy-1.11.0/mapproxy/config_template/paster/etc/mapproxy.yaml mapproxy-1.12.0/mapproxy/config_template/paster/etc/mapproxy.yaml --- mapproxy-1.11.0/mapproxy/config_template/paster/etc/mapproxy.yaml 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/config_template/paster/etc/mapproxy.yaml 1970-01-01 00:00:00.000000000 +0000 @@ -1,138 +0,0 @@ -services: - demo: - kml: - tms: - # needs no arguments - wms: - # srs: ['EPSG:4326', 'EPSG:900913'] - # image_formats: ['image/jpeg', 'image/png'] - md: - # metadata used in capabilities documents - title: MapProxy WMS Proxy - abstract: This is the fantastic MapProxy. - online_resource: http://mapproxy.org/ - contact: - person: Your Name Here - position: Technical Director - organization: - address: Fakestreet 123 - city: Somewhere - postcode: 12345 - country: Germany - phone: +49(0)000-000000-0 - fax: +49(0)000-000000-0 - email: info@omniscale.de - access_constraints: - Insert license and copyright information for this service. - fees: 'None' - -layers: - - name: osm - title: Omniscale OSM WMS - osm.omniscale.net - sources: [osm_cache] - # - name: osm_full_example - # title: Omniscale OSM WMS - osm.omniscale.net - # sources: [osm_cache_full_example] - -caches: - osm_cache: - grids: [GLOBAL_MERCATOR, global_geodetic_sqrt2] - sources: [osm_wms] - - # osm_cache_full_example: - # meta_buffer: 20 - # meta_size: [5, 5] - # format: image/png - # request_format: image/tiff - # link_single_color_images: true - # use_direct_from_level: 5 - # grids: [grid_full_example] - # sources: [osm_wms, overlay_full_example] - - -sources: - osm_wms: - type: wms - req: - url: http://osm.omniscale.net/proxy/service? - layers: osm - - # overlay_full_example: - # type: wms - # concurrent_requests: 4 - # wms_opts: - # version: 1.3.0 - # featureinfo: true - # supported_srs: ['EPSG:4326', 'EPSG:31467'] - # supported_formats: ['image/tiff', 'image/jpeg'] - # http: - # ssl_no_cert_checks: true - # req: - # url: https://user:password@example.org:81/service? - # layers: roads,rails - # styles: base,base - # transparent: true - # # # always request in this format - # # format: image/png - # map: /home/map/mapserver.map - - -grids: - global_geodetic_sqrt2: - base: GLOBAL_GEODETIC - res_factor: 'sqrt2' - # grid_full_example: - # tile_size: [512, 512] - # srs: 'EPSG:900913' - # bbox: [5, 45, 15, 55] - # bbox_srs: 'EPSG:4326' - # min_res: 2000 #m/px - # max_res: 50 #m/px - # align_resolutions_with: GLOBAL_MERCATOR - # another_grid_full_example: - # srs: 'EPSG:900913' - # bbox: [5, 45, 15, 55] - # bbox_srs: 'EPSG:4326' - # res_factor: 1.5 - # num_levels: 25 - -globals: - # # coordinate transformation options - # srs: - # # WMS 1.3.0 requires all coordiates in the correct axis order, - # # i.e. lon/lat or lat/lon. Use the following settings to - # # explicitly set a CRS to either North/East or East/North - # # ordering. - # axis_order_ne: ['EPSG:9999', 'EPSG:9998'] - # axis_order_en: ['EPSG:0000', 'EPSG:0001'] - # # you can set the proj4 data dir here, if you need custom - # # epsg definitions. the path must contain a file named 'epsg' - # # the format of the file is: - # # <4326> +proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs <> - # proj_data_dir: '/path to dir that contains epsg file' - - # # cache options - # cache: - # # where to store the cached images - # base_dir: '../var/cache_data' - # # where to store lockfiles - # lock_dir: '../tmp/tile_locks' - # # request x*y tiles in one step - # meta_size: [4, 4] - # # add a buffer on all sides (in pixel) when requesting - # # new images - # meta_buffer: 80 - - - # image/transformation options - image: - resampling_method: nearest - # resampling_method: bilinear - # resampling_method: bicubic - # jpeg_quality: 90 - # # stretch cached images by this factor before - # # using the next level - # stretch_factor: 1.15 - # # shrink cached images up to this factor before - # # returning an empty image (for the first level) - # max_shrink_factor: 4.0 diff -Nru mapproxy-1.11.0/mapproxy/config_template/paster/etc/seed.yaml mapproxy-1.12.0/mapproxy/config_template/paster/etc/seed.yaml --- mapproxy-1.11.0/mapproxy/config_template/paster/etc/seed.yaml 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/config_template/paster/etc/seed.yaml 1970-01-01 00:00:00.000000000 +0000 @@ -1,50 +0,0 @@ -seeds: - myseed1: - caches: [osm_cache] - grids: [GLOBAL_MERCATOR] - coverages: [austria] - levels: - to: 10 - refresh_before: - time: 2010-10-21T12:35:00 - - # dach: - # caches: [osm_roads] - # coverages: [germany, austria, switzerland] - # grids: [GLOBAL_MERCATOR, GLOBAL_GEODETIC] - # refresh_before: - # weeks: 1 - # levels: - # from: 11 - # to: 15 - -cleanups: - clean1: - caches: [osm_cache] - grids: [GLOBAL_MERCATOR] - remove_before: - days: 7 - hours: 3 - levels: [2,3,5,7] - - # clean2: - # caches: [osm_roads] - # grids: [GLOBAL_MERCATOR] - # coverages: [germany, austria, switzerland] - # remove_before: - # time: 2011-01-31T12:00:00 - # levels: - # from: 11 - # to: 14 - -coverages: - austria: - bbox: [9.36, 46.33, 17.28, 49.09] - bbox_srs: EPSG:4326 - # germany: - # ogr_datasource: 'shps/world_boundaries_m.shp' - # ogr_where: 'CNTRY_NAME = "Germany"' - # ogr_srs: 'EPSG:900913' - # switzerland: - # polygons: 'polygons/SZ.txt' - # polygons_srs: EPSG:900913 \ No newline at end of file diff -Nru mapproxy-1.11.0/mapproxy/featureinfo.py mapproxy-1.12.0/mapproxy/featureinfo.py --- mapproxy-1.11.0/mapproxy/featureinfo.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/featureinfo.py 2019-08-30 07:34:08.000000000 +0000 @@ -23,12 +23,14 @@ try: from lxml import etree, html + has_xslt_support = True - etree, html # prevent pyflakes warning + etree, html # prevent pyflakes warning except ImportError: has_xslt_support = False etree = html = None + class FeatureInfoDoc(object): content_type = None @@ -40,7 +42,7 @@ class TextFeatureInfoDoc(FeatureInfoDoc): - info_type = 'text' + info_type = "text" def __init__(self, content): self.content = content @@ -51,10 +53,11 @@ @classmethod def combine(cls, docs): result_content = [doc.as_string() for doc in docs] - return cls(b'\n'.join(result_content)) + return cls(b"\n".join(result_content)) + class XMLFeatureInfoDoc(FeatureInfoDoc): - info_type = 'xml' + info_type = "xml" def __init__(self, content): if isinstance(content, (string_type, bytes)): @@ -62,10 +65,10 @@ self._etree = None else: self._str_content = None - if hasattr(content, 'getroottree'): + if hasattr(content, "getroottree"): content = content.getroottree() self._etree = content - assert hasattr(content, 'getroot'), "expected etree like object" + assert hasattr(content, "getroot"), "expected etree like object" def as_string(self): if self._str_content is None: @@ -86,7 +89,8 @@ @classmethod def combine(cls, docs): - if etree is None: return TextFeatureInfoDoc.combine(docs) + if etree is None: + return TextFeatureInfoDoc.combine(docs) doc = docs.pop(0) result_tree = copy.deepcopy(doc.as_etree()) for doc in docs: @@ -95,8 +99,9 @@ return cls(result_tree) + class HTMLFeatureInfoDoc(XMLFeatureInfoDoc): - info_type = 'html' + info_type = "html" def _parse_content(self): root = html.document_fromstring(self._str_content) @@ -126,7 +131,7 @@ class JSONFeatureInfoDoc(FeatureInfoDoc): - info_type = 'json' + info_type = "json" def __init__(self, content): self.content = content @@ -136,7 +141,12 @@ @classmethod def combine(cls, docs): - contents = [json.loads(d.content) for d in docs] + contents = [] + for d in docs: + content = d.content + if not isinstance(content, string_type): + content = content.decode('UTF-8') + contents.append(json.loads(content)) combined = reduce(lambda a, b: merge_dict(a, b), contents) return cls(json.dumps(combined)) @@ -157,31 +167,52 @@ base[k] = v return base + def create_featureinfo_doc(content, info_format): - info_format = info_format.split(';', 1)[0].strip() # remove mime options like charset - if info_format in ('text/xml', 'application/vnd.ogc.gml'): + info_type = featureinfo_type(info_format) + if info_type == "xml": return XMLFeatureInfoDoc(content) - if info_format == 'text/html': + if info_type == "html": return HTMLFeatureInfoDoc(content) - if info_format == 'application/json': + if info_type == "json": return JSONFeatureInfoDoc(content) return TextFeatureInfoDoc(content) +def featureinfo_type(info_format): + info_format = info_format.split(";", 1)[ + 0 + ].strip() # remove mime options like charset + if info_format in ("text/xml", "application/xml", + "application/gml+xml", "application/vnd.ogc.gml"): + return "xml" + if info_format == "text/html": + return "html" + if info_format == "application/json": + return "json" + + return "text" + class XSLTransformer(object): - def __init__(self, xsltscript): + + def __init__(self, xsltscript, info_format=None): self.xsltscript = xsltscript + self.info_type = featureinfo_type(info_format or "text/xml") def transform(self, input_doc): input_tree = input_doc.as_etree() xslt_tree = etree.parse(self.xsltscript) transform = etree.XSLT(xslt_tree) output_tree = transform(input_tree) - return XMLFeatureInfoDoc(output_tree) + if self.info_type == "html": + return HTMLFeatureInfoDoc(output_tree) + else: + return XMLFeatureInfoDoc(output_tree) __call__ = transform + def as_io(doc): if PY2: return BytesIO(doc) @@ -192,10 +223,27 @@ return BytesIO(doc) -def combined_inputs(input_docs): - doc = input_docs.pop(0) - input_tree = etree.parse(as_io(doc)) - for doc in input_docs: - doc_tree = etree.parse(as_io(doc)) - input_tree.getroot().extend(doc_tree.getroot().iterchildren()) - return input_tree +def combine_docs(docs, transformer=None): + """ + Combine multiple FeatureInfoDocs. + + Combines as text, if the type of the docs differ. + Otherwise the type specifix combine is called. + + Returns the combined document and the info_type (text, xml or json). + info_type is None if the output is transformed, as the type is dependent on the + transformer. + """ + if len(set(d.info_type for d in docs)) > 1: + # more then one info_type, combine as plain text + doc = TextFeatureInfoDoc.combine(docs) + infotype = "text" + else: + # all same type, combine with type specific handler + infotype = docs[0].info_type + doc = docs[0].combine(docs) + + if transformer: + doc = transformer(doc) + infotype = None # defined by transformer + return doc.as_string(), infotype diff -Nru mapproxy-1.11.0/mapproxy/image/__init__.py mapproxy-1.12.0/mapproxy/image/__init__.py --- mapproxy-1.11.0/mapproxy/image/__init__.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/image/__init__.py 2019-08-30 07:34:08.000000000 +0000 @@ -19,10 +19,11 @@ import io from io import BytesIO -from mapproxy.compat.image import Image, ImageChops + +from mapproxy.compat.image import Image, ImageChops, ImageFileDirectory_v2, TiffTags from mapproxy.image.opts import create_image, ImageFormat from mapproxy.config import base_config -from mapproxy.srs import make_lin_transf +from mapproxy.srs import make_lin_transf, get_epsg_num from mapproxy.compat import string_type import logging @@ -30,6 +31,7 @@ log = logging.getLogger('mapproxy.image') + magic_bytes = [ ('png', (b"\211PNG\r\n\032\n",)), ('jpeg', (b"\xFF\xD8",)), @@ -46,6 +48,47 @@ return format return None +TIFF_MODELPIXELSCALETAG = 33550 +TIFF_MODELTIEPOINTTAG = 33922 +TIFF_GEOKEYDIRECTORYTAG = 34735 + +class GeoReference(object): + def __init__(self, bbox, srs): + self.bbox = bbox + self.srs = srs + + + def tiepoints(self): + return ( + 0.0, 0.0, 0.0, + self.bbox[0], self.bbox[3], 0.0, + ) + + def pixelscale(self, img_size): + width = self.bbox[2] - self.bbox[0] + height = self.bbox[3] - self.bbox[1] + return ( + float(width)/img_size[0], float(height)/img_size[1], 0.0, + ) + + def tiff_tags(self, img_size): + tags = ImageFileDirectory_v2() + tags[TIFF_MODELPIXELSCALETAG] = self.pixelscale(img_size) + tags.tagtype[TIFF_MODELPIXELSCALETAG] = TiffTags.DOUBLE + tags[TIFF_MODELTIEPOINTTAG] = self.tiepoints() + tags.tagtype[TIFF_MODELTIEPOINTTAG] = TiffTags.DOUBLE + + model_type = 2 if self.srs.is_latlong else 1 + tags[TIFF_GEOKEYDIRECTORYTAG] = ( + 1, 1, 0, 4, # {KeyDirectoryVersion, KeyRevision, MinorRevision, NumberOfKeys} + 1024, 0, 1, model_type, # 1 projected, 2 geographic (lat/long) + 1025, 0, 1, 1, # 1 RasterIsArea, 2 RasterIsPoint + 3072, 0, 1, get_epsg_num(self.srs.srs_code), + ) + tags.tagtype[TIFF_GEOKEYDIRECTORYTAG] = TiffTags.SHORT + return tags + + class ImageSource(object): """ This class wraps either a PIL image, a file-like object, or a file name. @@ -53,7 +96,7 @@ object (`as_buffer`). """ - def __init__(self, source, size=None, image_opts=None, cacheable=True): + def __init__(self, source, size=None, image_opts=None, cacheable=True, georef=None): """ :param source: the image :type source: PIL `Image`, image file object, or filename @@ -67,8 +110,14 @@ self.image_opts = image_opts self._size = size self.cacheable = cacheable + self.georef = georef + + @property + def source(self): + return self._img or self._buf or self._fname - def _set_source(self, source): + @source.setter + def source(self, source): self._img = None self._buf = None if isinstance(source, string_type): @@ -78,11 +127,6 @@ else: self._buf = source - def _get_source(self): - return self._img or self._buf or self._fname - - source = property(_get_source, _set_source) - def close_buffers(self): if self._buf: try: @@ -90,9 +134,6 @@ except IOError: pass - if self._img: - self._img = None - @property def filename(self): return self._fname @@ -156,7 +197,7 @@ if image_opts is None: image_opts = self.image_opts log.debug('image -> buf(%s)' % (image_opts.format,)) - self._buf = img_to_buf(self._img, image_opts=image_opts) + self._buf = img_to_buf(self._img, image_opts=image_opts, georef=self.georef) else: self._make_seekable_buf() if seekable else self._make_readable_buf() if self.image_opts and image_opts and not self.image_opts.format and image_opts.format: @@ -269,7 +310,7 @@ return any(img.histogram()[-256:-1]) return False -def img_to_buf(img, image_opts): +def img_to_buf(img, image_opts, georef=None): defaults = {} image_opts = image_opts.copy() @@ -313,6 +354,16 @@ else: defaults['quality'] = base_config().image.jpeg_quality + elif format == 'tiff': + if georef: + tags = georef.tiff_tags(img.size) + defaults['tiffinfo'] = tags + if 'tiff_compression' in image_opts.encoding_options: + defaults['compression'] = image_opts.encoding_options['tiff_compression'] + if defaults['compression'] == 'jpeg': + if 'jpeg_quality' in image_opts.encoding_options: + defaults['quality'] = image_opts.encoding_options['jpeg_quality'] + # unsupported transparency tuple can still be in non-RGB img.infos # see: https://github.com/python-pillow/Pillow/pull/2633 if format == 'png' and img.mode != 'RGB' and 'transparency' in img.info and isinstance(img.info['transparency'], tuple): diff -Nru mapproxy-1.11.0/mapproxy/image/opts.py mapproxy-1.12.0/mapproxy/image/opts.py --- mapproxy-1.11.0/mapproxy/image/opts.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/image/opts.py 2019-08-30 07:34:08.000000000 +0000 @@ -179,4 +179,4 @@ options.transparent = transparent options.mode = mode - return options \ No newline at end of file + return options diff -Nru mapproxy-1.11.0/mapproxy/layer.py mapproxy-1.12.0/mapproxy/layer.py --- mapproxy-1.11.0/mapproxy/layer.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/layer.py 2019-08-30 07:34:08.000000000 +0000 @@ -23,7 +23,7 @@ from mapproxy.image import SubImageSource, bbox_position_in_image from mapproxy.image.opts import ImageOptions from mapproxy.image.tile import TiledImage -from mapproxy.srs import SRS, bbox_equals, merge_bbox, make_lin_transf +from mapproxy.srs import SRS, bbox_equals, merge_bbox, make_lin_transf, SupportedSRS from mapproxy.proj import ProjError from mapproxy.compat import iteritems @@ -268,6 +268,9 @@ min(source[3], sub[3])), self.srs) + def transform(self, srs): + return MapExtent(self.bbox_for(srs), srs) + class DefaultMapExtent(MapExtent): """ Default extent that covers the whole world. @@ -325,18 +328,17 @@ class SRSConditional(MapLayer): supports_meta_tiles = True - PROJECTED = 'PROJECTED' - GEOGRAPHIC = 'GEOGRAPHIC' - def __init__(self, layers, extent, opacity=None): + def __init__(self, layers, extent, opacity=None, preferred_srs=None): MapLayer.__init__(self) - # TODO geographic/projected fallback self.srs_map = {} self.res_range = merge_layer_res_ranges([l[0] for l in layers]) - for layer, srss in layers: - for srs in srss: - self.srs_map[srs] = layer + supported_srs = [] + for layer, srs in layers: + supported_srs.append(srs) + self.srs_map[srs] = layer + self.supported_srs = SupportedSRS(supported_srs, preferred_srs) self.extent = extent self.opacity = opacity @@ -346,24 +348,8 @@ return layer.get_map(query) def _select_layer(self, query_srs): - # srs exists - if query_srs in self.srs_map: - return self.srs_map[query_srs] - - # srs_type exists - srs_type = self.GEOGRAPHIC if query_srs.is_latlong else self.PROJECTED - if srs_type in self.srs_map: - return self.srs_map[srs_type] - - # first with same type - is_latlong = query_srs.is_latlong - for srs in self.srs_map: - if hasattr(srs, 'is_latlong') and srs.is_latlong == is_latlong: - return self.srs_map[srs] - - # return first - return self.srs_map.itervalues().next() - + srs = self.supported_srs.best_srs(query_srs) + return self.srs_map[srs] class DirectMapLayer(MapLayer): supports_meta_tiles = True @@ -398,7 +384,9 @@ self.tile_manager = tile_manager self.grid = tile_manager.grid self.extent = extent or map_extent_from_grid(self.grid) - self.res_range = merge_layer_res_ranges(self.tile_manager.sources) + self.res_range = [] + if not self.tile_manager.rescale_tiles: + self.res_range = merge_layer_res_ranges(self.tile_manager.sources) self.max_tile_limit = max_tile_limit def get_map(self, query): diff -Nru mapproxy-1.11.0/mapproxy/request/wms/__init__.py mapproxy-1.12.0/mapproxy/request/wms/__init__.py --- mapproxy-1.11.0/mapproxy/request/wms/__init__.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/request/wms/__init__.py 2019-08-30 07:34:08.000000000 +0000 @@ -72,8 +72,8 @@ """ if 'height' not in self or 'width' not in self: return None - width = int(self.params['width']) - height = int(self.params['height']) + width = int(float(self.params['width'])) # allow float sizes (100.0), but truncate decimals + height = int(float(self.params['height'])) return (width, height) def _set_size(self, value): self['width'] = str(value[0]) @@ -632,7 +632,7 @@ if 'wmtver' in req.args: return Version(req.args['wmtver']) - return Version('1.1.1') # default + return None def _parse_request_type(req): if 'request' in req.args: @@ -684,7 +684,16 @@ version = _parse_version(req) req_type = _parse_request_type(req) - if versions and version not in versions: + if versions is None: + versions = sorted([ + Version(v) for v in ('1.0.0', '1.1.0', '1.1.1', '1.3.0')]) + + if version is None: + # If no version number is specified in the request, + # the server shall respond with the highest version. + version = max(versions) + + if version not in versions: version_requests = None else: version_requests = request_mapping.get(version, None) diff -Nru mapproxy-1.11.0/mapproxy/request/wmts.py mapproxy-1.12.0/mapproxy/request/wmts.py --- mapproxy-1.11.0/mapproxy/request/wmts.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/request/wmts.py 2019-08-30 07:34:08.000000000 +0000 @@ -148,7 +148,7 @@ WMTSRequest.__init__(self, param=param, url=url, validate=validate, non_strict=non_strict, **kw) - def make_tile_request(self): + def make_request(self): self.layer = self.params.layer self.tilematrixset = self.params.tilematrixset self.format = self.params.format # TODO @@ -212,6 +212,10 @@ expected_param = WMTS100TileRequest.expected_param[:] + ['infoformat', 'i', 'j'] non_strict_params = set(['format', 'styles']) + def make_request(self): + WMTS100TileRequest.make_request(self) + self.infoformat = self.params.infoformat + self.pos = self.params.pos class WMTS100CapabilitiesRequest(WMTSRequest): request_handler_name = 'capabilities' @@ -281,9 +285,12 @@ 'TileMatrix': r'\d+', 'TileRow': r'-?\d+', 'TileCol': r'-?\d+', + 'I': r'\d+', + 'J': r'\d+', 'Style': r'[\w_.:-]+', 'Layer': r'[\w_.:-]+', - 'Format': r'\w+' + 'Format': r'\w+', + 'InfoFormat': r'\w+', } required = set(['TileCol', 'TileRow', 'TileMatrix', 'TileMatrixSet', 'Layer']) @@ -311,41 +318,74 @@ if self._regexp: return self._regexp converted_re = self.var_re.sub(self.substitute_var, re.escape(self.template)) - wmts_re = re.compile(converted_re) + wmts_re = re.compile('/wmts' + converted_re) if not self.found.issuperset(self.required): raise InvalidWMTSTemplate('missing required variables in WMTS restful template: %s' % self.required.difference(self.found)) self._regexp = wmts_re return wmts_re +class FeatureInfoURLTemplateConverter(URLTemplateConverter): + required = set(['TileCol', 'TileRow', 'TileMatrix', 'TileMatrixSet', 'Layer', 'I', 'J']) + class WMTS100RestTileRequest(TileRequest): """ - Class for TMS-like KML requests. + Class for RESTful WMTS requests. """ xml_exception_handler = WMTS100ExceptionHandler request_handler_name = 'tile' origin = 'nw' - url_converter = None - def __init__(self, request): + def __init__(self, request, req_vars, url_converter=None): self.http = request self.url = request.base_url self.dimensions = {} + self.req_vars = req_vars + self.url_converter = url_converter - def make_tile_request(self): + def make_request(self): """ Initialize tile request. Sets ``tile`` and ``layer`` and ``format``. :raise RequestError: if the format does not match the URL template`` """ - match = self.tile_req_re.search(self.http.path) - if not match: - raise RequestError('invalid request (%s)' % (self.http.path), request=self) + req_vars = self.req_vars + self.layer = req_vars['Layer'] + self.tile = int(req_vars['TileCol']), int(req_vars['TileRow']), int(req_vars['TileMatrix']) + self.format = req_vars.get('Format') + self.tilematrixset = req_vars['TileMatrixSet'] + if self.url_converter and self.url_converter.dimensions: + for dim in self.url_converter.dimensions: + self.dimensions[dim.lower()] = req_vars[dim.lower()] - req_vars = match.groupdict() + @property + def exception_handler(self): + return self.xml_exception_handler() +class WMTS100RestFeatureInfoRequest(TileRequest): + """ + Class for RESTful WMTS FeatureInfo requests. + """ + xml_exception_handler = WMTS100ExceptionHandler + request_handler_name = 'featureinfo' + + def __init__(self, request, req_vars, url_converter=None): + self.http = request + self.url = request.base_url + self.dimensions = {} + self.req_vars = req_vars + self.url_converter = url_converter + + def make_request(self): + """ + Initialize tile request. Sets ``tile`` and ``layer`` and ``format``. + :raise RequestError: if the format does not match the URL template`` + """ + req_vars = self.req_vars self.layer = req_vars['Layer'] self.tile = int(req_vars['TileCol']), int(req_vars['TileRow']), int(req_vars['TileMatrix']) + self.pos = int(req_vars['I']), int(req_vars['J']) self.format = req_vars.get('Format') + self.infoformat = req_vars.get('InfoFormat') self.tilematrixset = req_vars['TileMatrixSet'] if self.url_converter and self.url_converter.dimensions: for dim in self.url_converter.dimensions: @@ -374,14 +414,23 @@ return self.xml_exception_handler() -def make_wmts_rest_request_parser(url_converter_): - class WMTSRequestWrapper(WMTS100RestTileRequest): - url_converter = url_converter_ - tile_req_re = url_converter.regexp() +def make_wmts_rest_request_parser(url_converter, fi_url_converter): + tile_req_re = url_converter.regexp() + fi_req_re = fi_url_converter.regexp() def wmts_request(req): if req.path.endswith(RESTFUL_CAPABILITIES_PATH): return WMTS100RestCapabilitiesRequest(req) - return WMTSRequestWrapper(req) + + match = tile_req_re.search(req.path) + if not match: + match = fi_req_re.search(req.path) + if not match: + raise RequestError('invalid request (%s)' % (req.path)) + + req_vars = match.groupdict() + return WMTS100RestFeatureInfoRequest(req, req_vars, fi_url_converter) + req_vars = match.groupdict() + return WMTS100RestTileRequest(req, req_vars, url_converter) return wmts_request diff -Nru mapproxy-1.11.0/mapproxy/response.py mapproxy-1.12.0/mapproxy/response.py --- mapproxy-1.11.0/mapproxy/response.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/response.py 2019-08-30 07:34:08.000000000 +0000 @@ -140,6 +140,9 @@ def fixed_headers(self): headers = [] for key, value in iteritems(self.headers): + if type(value) != text_type: + # for str subclasses like ImageFormat + value = str(value) if PY2 and isinstance(value, unicode): value = value.encode('utf-8') headers.append((key, value)) diff -Nru mapproxy-1.11.0/mapproxy/script/conf/app.py mapproxy-1.12.0/mapproxy/script/conf/app.py --- mapproxy-1.11.0/mapproxy/script/conf/app.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/script/conf/app.py 2019-08-30 07:34:08.000000000 +0000 @@ -143,12 +143,12 @@ overwrite = None if options.overwrite: with open(options.overwrite, 'rb') as f: - overwrite = yaml.load(f) + overwrite = yaml.safe_load(f) overwrite_seed = None if options.overwrite_seed: with open(options.overwrite_seed, 'rb') as f: - overwrite_seed = yaml.load(f) + overwrite_seed = yaml.safe_load(f) conf = {} if options.base: @@ -189,4 +189,4 @@ write_header(f, options.capabilities) yaml.dump(seed_conf, f, default_flow_style=False, Dumper=MapProxyYAMLDumper) - return 0 \ No newline at end of file + return 0 diff -Nru mapproxy-1.11.0/mapproxy/script/conf/utils.py mapproxy-1.12.0/mapproxy/script/conf/utils.py --- mapproxy-1.11.0/mapproxy/script/conf/utils.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/script/conf/utils.py 2019-08-30 07:34:08.000000000 +0000 @@ -109,7 +109,7 @@ canonical=None, indent=None, width=None, allow_unicode=None, line_break=None, encoding=None, explicit_start=None, explicit_end=None, - version=None, tags=None): + version=None, tags=None, sort_keys=None): Emitter.__init__(self, stream, canonical=canonical, indent=indent, width=width, allow_unicode=allow_unicode, line_break=line_break) diff -Nru mapproxy-1.11.0/mapproxy/script/export.py mapproxy-1.12.0/mapproxy/script/export.py --- mapproxy-1.11.0/mapproxy/script/export.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/script/export.py 2019-08-30 07:34:08.000000000 +0000 @@ -65,7 +65,7 @@ grid_conf = {} for arg in args: key, value = arg.split('=') - value = yaml.load(value) + value = yaml.safe_load(value) grid_conf[key] = value validate(conf_spec.grid_opts, grid_conf) @@ -93,7 +93,7 @@ return '\n'.join(info) def export_command(args=None): - parser = optparse.OptionParser("%prog grids [options] mapproxy_conf") + parser = optparse.OptionParser("%prog export [options] mapproxy_conf") parser.add_option("-f", "--mapproxy-conf", dest="mapproxy_conf", help="MapProxy configuration") diff -Nru mapproxy-1.11.0/mapproxy/seed/cleanup.py mapproxy-1.12.0/mapproxy/seed/cleanup.py --- mapproxy-1.11.0/mapproxy/seed/cleanup.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/seed/cleanup.py 2019-08-30 07:34:08.000000000 +0000 @@ -47,9 +47,11 @@ if callable(getattr(task.tile_manager.cache, 'level_location', None)): simple_cleanup(task, dry_run=dry_run, progress_logger=progress_logger, cleanup_progress=cleanup_progress) + task.tile_manager.cleanup() continue elif callable(getattr(task.tile_manager.cache, 'remove_level_tiles_before', None)): cache_cleanup(task, dry_run=dry_run, progress_logger=progress_logger) + task.tile_manager.cleanup() continue tilewalker_cleanup(task, dry_run=dry_run, concurrency=concurrency, @@ -57,6 +59,7 @@ progress_logger=progress_logger, seed_progress=seed_progress, ) + task.tile_manager.cleanup() def simple_cleanup(task, dry_run, progress_logger=None, cleanup_progress=None): diff -Nru mapproxy-1.11.0/mapproxy/seed/config.py mapproxy-1.12.0/mapproxy/seed/config.py --- mapproxy-1.11.0/mapproxy/seed/config.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/seed/config.py 2019-08-30 07:34:08.000000000 +0000 @@ -314,7 +314,14 @@ self.refresh_timestamp = 0 md = dict(name=self.name, cache_name=cache_name, grid_name=grid_name) - yield SeedTask(md, tile_manager, levels, self.refresh_timestamp, coverage) + + if tile_manager.rescale_tiles: + if tile_manager.rescale_tiles > 0: + levels = levels[::-1] + for l in levels: + yield SeedTask(md, tile_manager, [l], self.refresh_timestamp, coverage) + else: + yield SeedTask(md, tile_manager, levels, self.refresh_timestamp, coverage) class CleanupConfiguration(ConfigurationBase): def __init__(self, name, conf, seeding_conf): diff -Nru mapproxy-1.11.0/mapproxy/seed/seeder.py mapproxy-1.12.0/mapproxy/seed/seeder.py --- mapproxy-1.11.0/mapproxy/seed/seeder.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/seed/seeder.py 2019-08-30 07:34:08.000000000 +0000 @@ -442,7 +442,7 @@ @property def id(self): - return self.md['name'], self.md['cache_name'], self.md['grid_name'] + return self.md['name'], self.md['cache_name'], self.md['grid_name'], tuple(self.levels) def intersects(self, bbox): if self.coverage.contains(bbox, self.grid.srs): return CONTAINS @@ -507,11 +507,18 @@ if task.refresh_timestamp is not None: task.tile_manager._expire_timestamp = task.refresh_timestamp task.tile_manager.minimize_meta_requests = False + + work_on_metatiles = True + if task.tile_manager.rescale_tiles: + work_on_metatiles = False + tile_worker_pool = TileWorkerPool(task, TileSeedWorker, dry_run=dry_run, size=concurrency, progress_logger=progress_logger) tile_walker = TileWalker(task, tile_worker_pool, handle_uncached=True, skip_geoms_for_last_levels=skip_geoms_for_last_levels, progress_logger=progress_logger, - seed_progress=seed_progress) + seed_progress=seed_progress, + work_on_metatiles=work_on_metatiles, + ) try: tile_walker.walk() except KeyboardInterrupt: diff -Nru mapproxy-1.11.0/mapproxy/seed/util.py mapproxy-1.12.0/mapproxy/seed/util.py --- mapproxy-1.11.0/mapproxy/seed/util.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/seed/util.py 2019-08-30 07:34:08.000000000 +0000 @@ -191,7 +191,7 @@ time.sleep(0.01) except exceptions as ex: if n >= max_repeat: - print >>sys.stderr, "An error occured. Giving up" + print("An error occured. Giving up", file=sys.strerr) raise BackoffError wait_for = start_backoff_sec * 2**n if wait_for > max_backoff: diff -Nru mapproxy-1.11.0/mapproxy/service/demo.py mapproxy-1.12.0/mapproxy/service/demo.py --- mapproxy-1.11.0/mapproxy/service/demo.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/service/demo.py 2019-08-30 07:34:08.000000000 +0000 @@ -22,7 +22,6 @@ import pkg_resources import mimetypes from collections import defaultdict -from xml.sax.saxutils import escape from mapproxy.config.config import base_config from mapproxy.compat import PY2 @@ -258,3 +257,15 @@ return True return False return True + + +def escape(data): + """ + Escape user-provided input data for safe inclusion in HTML _and_ JS to prevent XSS. + """ + data = data.replace('&', '&') + data = data.replace('>', '>') + data = data.replace('<', '<') + data = data.replace("'", '') + data = data.replace('"', '') + return data diff -Nru mapproxy-1.11.0/mapproxy/service/templates/wms100capabilities.xml mapproxy-1.12.0/mapproxy/service/templates/wms100capabilities.xml --- mapproxy-1.11.0/mapproxy/service/templates/wms100capabilities.xml 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/service/templates/wms100capabilities.xml 2019-08-30 07:34:08.000000000 +0000 @@ -66,7 +66,7 @@ -{{def layer_capabilities(layer, with_srs)}} +{{def layer_capabilities(layer, with_srs=False)}} {{if layer.name}} {{ layer.name }} diff -Nru mapproxy-1.11.0/mapproxy/service/templates/wms110capabilities.xml mapproxy-1.12.0/mapproxy/service/templates/wms110capabilities.xml --- mapproxy-1.11.0/mapproxy/service/templates/wms110capabilities.xml 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/service/templates/wms110capabilities.xml 2019-08-30 07:34:08.000000000 +0000 @@ -101,7 +101,7 @@ {{endif}} -{{def layer_capabilities(layer, with_srs)}} +{{def layer_capabilities(layer, with_srs=False)}} {{if layer.name}} {{ layer.name }} diff -Nru mapproxy-1.11.0/mapproxy/service/templates/wms111capabilities.xml mapproxy-1.12.0/mapproxy/service/templates/wms111capabilities.xml --- mapproxy-1.11.0/mapproxy/service/templates/wms111capabilities.xml 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/service/templates/wms111capabilities.xml 2019-08-30 07:34:08.000000000 +0000 @@ -113,7 +113,7 @@ {{endif}} -{{def layer_capabilities(layer, with_srs)}} +{{def layer_capabilities(layer, with_srs=False)}} {{if layer.name}} {{ layer.name }} diff -Nru mapproxy-1.11.0/mapproxy/service/templates/wms130capabilities.xml mapproxy-1.12.0/mapproxy/service/templates/wms130capabilities.xml --- mapproxy-1.11.0/mapproxy/service/templates/wms130capabilities.xml 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/service/templates/wms130capabilities.xml 2019-08-30 07:34:08.000000000 +0000 @@ -41,6 +41,10 @@ {{endif}} {{service.get('fees', 'none')}} {{service.get('access_constraints', 'none')}} +{{if max_output_size}} + {{max_output_size[0]}} + {{max_output_size[1]}} +{{endif}} @@ -93,7 +97,7 @@ {{if inspire_md}} -{{def inspire_dates(config)}} +{{def inspire_dates(config={})}} {{if 'date_of_publication' in config}} {{config['date_of_publication']}} {{endif}} @@ -196,7 +200,7 @@ {{endif}} {{endif}} -{{def layer_capabilities(layer, with_srs)}} +{{def layer_capabilities(layer, with_srs=False)}} {{py: md = bunch(default='', **layer.md)}} @@ -293,7 +297,7 @@ {{enddef}} -{{def layer_meta_tag(tag, config)}} +{{def layer_meta_tag(tag, config={})}} {{for data in config}} {{py: data=bunch(default='', **data)}} <{{tag}}{{if data.type}} type="{{data.type|escape}}"{{endif}}> diff -Nru mapproxy-1.11.0/mapproxy/service/templates/wmts100capabilities.xml mapproxy-1.12.0/mapproxy/service/templates/wmts100capabilities.xml --- mapproxy-1.11.0/mapproxy/service/templates/wmts100capabilities.xml 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/service/templates/wmts100capabilities.xml 2019-08-30 07:34:08.000000000 +0000 @@ -4,14 +4,14 @@ {{service.title}} {{service.abstract}} {{if service.keyword_list and len(service.keyword_list) > 0}} - + {{for list in service.keyword_list}} {{py: kw=bunch(default='', **list)}} {{for keyword in kw.keywords}} {{keyword}} {{endfor}} {{endfor}} - + {{endif}} OGC WMTS 1.0.0 @@ -70,6 +70,19 @@ + + + + + + + KVP + + + + + + {{endif}} @@ -86,6 +99,11 @@ default image/{{layer.format}} + {{if layer.queryable}} + {{for format in info_formats.values()}} + {{ format }} + {{endfor}} + {{endif}} {{for dimension in layer.dimensions}} {{ dimension_keys[dimension] }} @@ -102,7 +120,14 @@ {{endfor}} {{if restful}} + template="{{format_resource_template(layer, resource_template, service)}}"/> + + {{if layer.queryable}} + {{for suffix, mimetype in info_formats.items()}} + + {{endfor}} + {{endif}} + {{endif}} {{endfor}} diff -Nru mapproxy-1.11.0/mapproxy/service/tile.py mapproxy-1.12.0/mapproxy/service/tile.py --- mapproxy-1.11.0/mapproxy/service/tile.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/service/tile.py 2019-08-30 07:34:08.000000000 +0000 @@ -203,7 +203,7 @@ return template.substitute(service=bunch(default='', **service)) class TileLayer(object): - def __init__(self, name, title, md, tile_manager, dimensions=None): + def __init__(self, name, title, md, tile_manager, info_sources=[], dimensions=None): """ :param md: the layer metadata :param tile_manager: the layer tile manager @@ -212,6 +212,7 @@ self.title = title self.md = md self.tile_manager = tile_manager + self.info_sources = info_sources self.dimensions = dimensions self.grid = TileServiceGrid(tile_manager.grid) self.extent = map_extent_from_grid(self.grid) @@ -233,6 +234,10 @@ return format @property + def queryable(self): + return bool(self.info_sources) + + @property def format_mime_type(self): # force png format for capabilities & requests if mixed format if self._mixed_format: @@ -332,6 +337,58 @@ except SourceError as e: raise RequestError(e.args[0], request=tile_request, internal=True) + def get_info(self, info_request): + if info_request.format != self.format: + raise RequestError('invalid format (%s). this tile set only supports (%s)' + % (info_request.format, self.format), request=info_request, + code='InvalidParameterValue') + + tile_coord = self._internal_tile_coord(info_request) + + coverage_intersects = False + if coverage: + tile_bbox = self.grid.tile_bbox(tile_coord) + if coverage.contains(tile_bbox, self.grid.srs): + pass + elif coverage.intersects(tile_bbox, self.grid.srs): + coverage_intersects = True + else: + return self.empty_response() + + dimensions = self.checked_dimensions(info_request) + + try: + with self.tile_manager.session(): + tile = self.tile_manager.load_tile_coord(tile_coord, + dimensions=dimensions, with_metadata=True) + if tile.source is None: + return self.empty_response() + + # Provide the wrapping WSGI app or filter the opportunity to process the + # image before it's wrapped up in a response + if decorate_img: + tile.source = decorate_img(tile.source) + + if coverage_intersects: + if self.empty_response_as_png: + format = 'png' + image_opts = ImageOptions(transparent=True, format='png') + else: + format = self.format + image_opts = tile.source.image_opts + + tile.source = mask_image_source_from_coverage( + tile.source, tile_bbox, self.grid.srs, coverage, image_opts) + + return TileResponse(tile, format=format, image_opts=image_opts) + + format = None if self._mixed_format else info_request.format + return TileResponse(tile, format=format, image_opts=self.tile_manager.image_opts) + except SourceError as e: + raise RequestError(e.args[0], request=info_request, internal=True) + + + class ImageResponse(object): """ Response from an image. diff -Nru mapproxy-1.11.0/mapproxy/service/wms.py mapproxy-1.12.0/mapproxy/service/wms.py --- mapproxy-1.11.0/mapproxy/service/wms.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/service/wms.py 2019-08-30 07:34:08.000000000 +0000 @@ -19,7 +19,9 @@ from mapproxy.compat import iteritems from mapproxy.compat.itertools import chain from functools import partial +from math import sqrt from mapproxy.cache.tile import CacheInfo +from mapproxy.featureinfo import combine_docs from mapproxy.request.wms import (wms_request, WMS111LegendGraphicRequest, mimetype_from_infotype, infotype_from_mimetype, switch_bbox_epsg_axis_order) from mapproxy.srs import SRS, TransformationError @@ -27,13 +29,13 @@ from mapproxy.response import Response from mapproxy.source import SourceError from mapproxy.exception import RequestError -from mapproxy.image import bbox_position_in_image, SubImageSource, BlankImageSource +from mapproxy.image import bbox_position_in_image, SubImageSource, BlankImageSource, GeoReference from mapproxy.image.merge import concat_legends, LayerMerger from mapproxy.image.opts import ImageOptions from mapproxy.image.message import attribution_image, message_image from mapproxy.layer import BlankImage, MapQuery, InfoQuery, LegendQuery, MapError, LimitedLayer from mapproxy.layer import MapBBOXError, merge_layer_extents, merge_layer_res_ranges -from mapproxy.util import async +from mapproxy.util import async_ from mapproxy.util.py import cached_property, reraise from mapproxy.util.coverage import load_limited_to from mapproxy.util.ext.odict import odict @@ -150,6 +152,7 @@ map_request.http.environ, (query.srs.srs_code, query.bbox)) try: + result.georef = GeoReference(bbox=orig_query.bbox, srs=orig_query.srs) result_buf = result.as_buffer(img_opts) except IOError as ex: raise RequestError('error while processing image file: %s' % ex, @@ -192,7 +195,7 @@ info_formats = [mimetype_from_infotype(map_request.version, info_type) for info_type in info_types] result = Capabilities(service, root_layer, tile_layers, self.image_formats, info_formats, srs=self.srs, srs_extents=self.srs_extents, - inspire_md=self.inspire_md, + inspire_md=self.inspire_md, max_output_pixels=self.max_output_pixels ).render(map_request) return Response(result, mimetype=map_request.mime_type) @@ -240,28 +243,22 @@ return Response('', mimetype=mimetype) if self.fi_transformers: - doc = infos[0].combine(infos) - if doc.info_type == 'text': - resp = doc.as_string() - mimetype = 'text/plain' - else: - if not mimetype: - if 'xml' in self.fi_transformers: - info_type = 'xml' - elif 'html' in self.fi_transformers: - info_type = 'html' - else: - info_type = 'text' - mimetype = mimetype_from_infotype(request.version, info_type) + if not mimetype: + if 'xml' in self.fi_transformers: + info_type = 'xml' + elif 'html' in self.fi_transformers: + info_type = 'html' else: - info_type = infotype_from_mimetype(request.version, mimetype) - resp = self.fi_transformers[info_type](doc).as_string() - else: - mimetype = mimetype_from_infotype(request.version, infos[0].info_type) - if len(infos) > 1: - resp = infos[0].combine(infos).as_string() + info_type = 'text' + mimetype = mimetype_from_infotype(request.version, info_type) else: - resp = infos[0].as_string() + info_type = infotype_from_mimetype(request.version, mimetype) + resp, actual_info_type = combine_docs(infos, self.fi_transformers[info_type]) + if actual_info_type is not None and info_type != actual_info_type: + mimetype = mimetype_from_infotype(request.version, actual_info_type) + else: + resp, info_type = combine_docs(infos) + mimetype = mimetype_from_infotype(request.version, info_type) return Response(resp, mimetype=mimetype) @@ -468,7 +465,7 @@ """ def __init__(self, server_md, layers, tile_layers, image_formats, info_formats, srs, srs_extents=None, epsg_axis_order=False, - inspire_md=None, + inspire_md=None, max_output_pixels=None ): self.service = server_md self.layers = layers @@ -478,9 +475,13 @@ self.srs = srs self.srs_extents = limit_srs_extents(srs_extents, srs) self.inspire_md = inspire_md + self.max_output_pixels = max_output_pixels def layer_srs_bbox(self, layer, epsg_axis_order=False): for srs, extent in iteritems(self.srs_extents): + if srs not in self.srs: + continue + # is_default is True when no explicit bbox is defined for this srs # use layer extent if extent.is_default: @@ -489,13 +490,15 @@ bbox = extent.bbox_for(SRS(srs)) else: # Use intersection of srs_extent and layer.extent. - bbox = extent.intersection(layer.extent).bbox_for(SRS(srs)) + # Use 4326 extents to avoid transformation errors. + a = extent.transform(SRS(4326)) + b = layer.extent.transform(SRS(4326)) + bbox = a.intersection(b).bbox_for(SRS(srs)) if epsg_axis_order: bbox = switch_bbox_epsg_axis_order(bbox, srs) - if srs in self.srs: - yield srs, bbox + yield srs, bbox # add native srs layer_srs_code = layer.extent.srs.srs_code @@ -520,6 +523,12 @@ inspire_md = None if self.inspire_md: inspire_md = recursive_bunch(default='', **self.inspire_md) + + max_output_size = None + if self.max_output_pixels: + output_width = output_height = int(sqrt(self.max_output_pixels)) + max_output_size = (output_width, output_height) + doc = template.substitute(service=bunch(default='', **self.service), layers=self.layers, formats=self.image_formats, @@ -529,6 +538,7 @@ layer_srs_bbox=self.layer_srs_bbox, layer_llbbox=self.layer_llbbox, inspire_md=inspire_md, + max_output_size=max_output_size, ) # strip blank lines doc = '\n'.join(l for l in doc.split('\n') if l.rstrip()) @@ -568,7 +578,7 @@ render_layers = combined_layers(self.layers, self.query) if not render_layers: return - async_pool = async.Pool(size=min(len(render_layers), self.concurrent_rendering)) + async_pool = async_.Pool(size=min(len(render_layers), self.concurrent_rendering)) if self.raise_source_errors: return self._render_raise_exceptions(async_pool, render_layers, layer_merger) diff -Nru mapproxy-1.11.0/mapproxy/service/wmts.py mapproxy-1.12.0/mapproxy/service/wmts.py --- mapproxy-1.11.0/mapproxy/service/wmts.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/service/wmts.py 2019-08-30 07:34:08.000000000 +0000 @@ -18,13 +18,18 @@ """ from __future__ import print_function +import re + from functools import partial from mapproxy.compat import iteritems, itervalues, iterkeys from mapproxy.request.wmts import ( wmts_request, make_wmts_rest_request_parser, URLTemplateConverter, + FeatureInfoURLTemplateConverter, ) +from mapproxy.layer import InfoQuery +from mapproxy.featureinfo import combine_docs from mapproxy.service.base import Server from mapproxy.response import Response from mapproxy.exception import RequestError @@ -41,13 +46,15 @@ class WMTSServer(Server): service = 'wmts' - def __init__(self, layers, md, request_parser=None, max_tile_age=None): + def __init__(self, layers, md, request_parser=None, max_tile_age=None, info_formats={}): Server.__init__(self) self.request_parser = request_parser or wmts_request self.md = md self.max_tile_age = max_tile_age self.layers, self.matrix_sets = self._matrix_sets(layers) self.capabilities_class = Capabilities + self.fi_transformers = None + self.info_formats = info_formats def _matrix_sets(self, layers): sets = {} @@ -72,7 +79,9 @@ def capabilities(self, request): service = self._service_md(request) layers = self.authorized_tile_layers(request.http.environ) - result = self.capabilities_class(service, layers, self.matrix_sets).render(request) + + + result = self.capabilities_class(service, layers, self.matrix_sets, info_formats=self.info_formats).render(request) return Response(result, mimetype='application/xml') def tile(self, request): @@ -99,25 +108,75 @@ resp.make_conditional(request.http) return resp - def authorize_tile_layer(self, tile_layer, request): - if 'mapproxy.authorize' in request.http.environ: - query_extent = tile_layer.grid.srs.srs_code, tile_layer.tile_bbox(request) - result = request.http.environ['mapproxy.authorize']('wmts', [tile_layer.name], - query_extent=query_extent, environ=request.http.environ) - if result['authorized'] == 'unauthenticated': - raise RequestError('unauthorized', status=401) - if result['authorized'] == 'full': - return - if result['authorized'] == 'partial': - if result['layers'].get(tile_layer.name, {}).get('tile', False) == True: - limited_to = result['layers'][tile_layer.name].get('limited_to') - if not limited_to: - limited_to = result.get('limited_to') - if limited_to: - return load_limited_to(limited_to) - else: - return None - raise RequestError('forbidden', status=403) + def featureinfo(self, request): + infos = [] + self.check_request(request, self.info_formats) + + tile_layer = self.layers[request.layer][request.tilematrixset] + if not request.format: + request.format = tile_layer.format + + feature_count = None + # WMTS REST style request do not have request params + if hasattr(request, 'params'): + feature_count = request.params.get('feature_count', None) + + bbox = tile_layer.grid.tile_bbox(request.tile) + query = InfoQuery(bbox, tile_layer.grid.tile_size, tile_layer.grid.srs, request.pos, + request.infoformat, feature_count=feature_count) + self.check_request_dimensions(tile_layer, request) + coverage = self.authorize_tile_layer(tile_layer, request, featureinfo=True) + + if not tile_layer.info_sources: + raise RequestError('layer %s not queryable' % str(request.layer), + code='OperationNotSupported', request=request) + + if coverage and not coverage.contains(query.coord, query.srs): + infos = [] + else: + for source in tile_layer.info_sources: + info = source.get_info(query) + if info is None: + continue + infos.append(info) + + mimetype = request.infoformat + + if not infos: + return Response('', mimetype=mimetype) + + resp, _ = combine_docs(infos) + + return Response(resp, mimetype=mimetype) + + def authorize_tile_layer(self, tile_layer, request, featureinfo=False): + if 'mapproxy.authorize' not in request.http.environ: + return + + query_extent = tile_layer.grid.srs.srs_code, tile_layer.tile_bbox(request) + + service = 'wmts' + key = 'tile' + if featureinfo: + service += '.featureinfo' + key = 'featureinfo' + + result = request.http.environ['mapproxy.authorize'](service, [tile_layer.name], + query_extent=query_extent, environ=request.http.environ) + if result['authorized'] == 'unauthenticated': + raise RequestError('unauthorized', status=401) + if result['authorized'] == 'full': + return + if result['authorized'] == 'partial': + if result['layers'].get(tile_layer.name, {}).get(key, False) == True: + limited_to = result['layers'][tile_layer.name].get('limited_to') + if not limited_to: + limited_to = result.get('limited_to') + if limited_to: + return load_limited_to(limited_to) + else: + return None + raise RequestError('forbidden', status=403) def authorized_tile_layers(self, env): if 'mapproxy.authorize' in env: @@ -137,8 +196,8 @@ else: return self.layers.values() - def check_request(self, request): - request.make_tile_request() + def check_request(self, request, info_formats=None): + request.make_request() if request.layer not in self.layers: raise RequestError('unknown layer: ' + str(request.layer), code='InvalidParameterValue', request=request) @@ -146,6 +205,18 @@ raise RequestError('unknown tilematrixset: ' + str(request.tilematrixset), code='InvalidParameterValue', request=request) + if info_formats is not None: + if '/' in request.infoformat: # mimetype + if request.infoformat not in self.info_formats.values(): + raise RequestError('unknown infoformat: ' + str(request.infoformat), + code='InvalidParameterValue', request=request) + else: # RESTful suffix + if request.infoformat not in self.info_formats: + raise RequestError('unknown infoformat: ' + str(request.infoformat), + code='InvalidParameterValue', request=request) + # set mimetype as infoformat + request.infoformat = self.info_formats[request.infoformat] + def check_request_dimensions(self, tile_layer, request): # allow arbitrary dimensions in KVP service # actual used values are checked later in TileLayer @@ -165,14 +236,18 @@ names = ('wmts',) request_methods = ('tile', 'capabilities') default_template = '/{Layer}/{TileMatrixSet}/{TileMatrix}/{TileCol}/{TileRow}.{Format}' + default_info_template = '/{Layer}/{TileMatrixSet}/{TileMatrix}/{TileCol}/{TileRow}/{I}/{J}.{InfoFormat}' - def __init__(self, layers, md, max_tile_age=None, template=None): + def __init__(self, layers, md, max_tile_age=None, template=None, fi_template=None, info_formats={}): WMTSServer.__init__(self, layers, md) self.max_tile_age = max_tile_age self.template = template or self.default_template + self.fi_template = fi_template or self.default_info_template + self.info_formats = info_formats self.url_converter = URLTemplateConverter(self.template) - self.request_parser = make_wmts_rest_request_parser(self.url_converter) - self.capabilities_class = partial(RestfulCapabilities, url_converter=self.url_converter) + self.fi_url_converter = FeatureInfoURLTemplateConverter(self.fi_template) + self.request_parser = make_wmts_rest_request_parser(self.url_converter, self.fi_url_converter) + self.capabilities_class = partial(RestfulCapabilities, url_converter=self.url_converter, fi_url_converter=self.fi_url_converter) def check_request_dimensions(self, tile_layer, request): # check that unknown dimension for this layer are set to default @@ -188,9 +263,10 @@ """ Renders WMS capabilities documents. """ - def __init__(self, server_md, layers, matrix_sets): + def __init__(self, server_md, layers, matrix_sets, info_formats={}): self.service = server_md self.layers = layers + self.info_formats = info_formats self.matrix_sets = matrix_sets def render(self, _map_request): @@ -200,6 +276,7 @@ return dict(service=bunch(default='', **self.service), restful=False, layers=self.layers, + info_formats=self.info_formats, tile_matrix_sets=self.matrix_sets) def _render_template(self, template): @@ -210,26 +287,27 @@ return doc class RestfulCapabilities(Capabilities): - def __init__(self, server_md, layers, matrix_sets, url_converter): - Capabilities.__init__(self, server_md, layers, matrix_sets) + def __init__(self, server_md, layers, matrix_sets, url_converter, fi_url_converter, info_formats={}): + Capabilities.__init__(self, server_md, layers, matrix_sets, info_formats=info_formats) self.url_converter = url_converter + self.fi_url_converter = fi_url_converter def template_context(self): return dict(service=bunch(default='', **self.service), restful=True, layers=self.layers, + info_formats=self.info_formats, tile_matrix_sets=self.matrix_sets, resource_template=self.url_converter.template, + fi_resource_template=self.fi_url_converter.template, # dimension_key maps lowercase dimensions to the actual # casing from the restful template dimension_keys=dict((k.lower(), k) for k in self.url_converter.dimensions), format_resource_template=format_resource_template, + format_info_resource_template=format_info_resource_template, ) def format_resource_template(layer, template, service): - # TODO: remove {{Format}} in 1.6 - if '{{Format}}' in template: - template = template.replace('{{Format}}', layer.format) if '{Format}' in template: template = template.replace('{Format}', layer.format) @@ -238,6 +316,15 @@ return service.url + template +def format_info_resource_template(layer, template, info_format, service): + if '{InfoFormat}' in template: + template = template.replace('{InfoFormat}', info_format) + + if '{Layer}' in template: + template = template.replace('{Layer}', layer.name) + + return service.url + template + class WMTSTileLayer(object): """ Wrap multiple TileLayers for the same cache but with different grids. @@ -257,7 +344,6 @@ return self.layers[gridname] -from mapproxy.grid import tile_grid # calculated from well-known scale set GoogleCRS84Quad METERS_PER_DEEGREE = 111319.4907932736 @@ -293,7 +379,3 @@ scale_denom=scale_denom, tile_size=self.grid.tile_size, ) - -if __name__ == '__main__': - print(TileMatrixSet(tile_grid(900913)).tile_matrixes()) - print(TileMatrixSet(tile_grid(4326, origin='ul')).tile_matrixes()) diff -Nru mapproxy-1.11.0/mapproxy/source/arcgis.py mapproxy-1.12.0/mapproxy/source/arcgis.py --- mapproxy-1.11.0/mapproxy/source/arcgis.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/source/arcgis.py 2019-08-30 07:34:08.000000000 +0000 @@ -21,11 +21,13 @@ class ArcGISSource(WMSSource): def __init__(self, client, image_opts=None, coverage=None, - res_range=None, supported_srs=None, supported_formats=None): + res_range=None, supported_srs=None, supported_formats=None, + error_handler=None): WMSSource.__init__(self, client, image_opts=image_opts, coverage=coverage, res_range=res_range, supported_srs=supported_srs, - supported_formats=supported_formats) + supported_formats=supported_formats, + error_handler=error_handler) class ArcGISInfoSource(WMSInfoSource): diff -Nru mapproxy-1.11.0/mapproxy/source/mapnik.py mapproxy-1.12.0/mapproxy/source/mapnik.py --- mapproxy-1.11.0/mapproxy/source/mapnik.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/source/mapnik.py 2019-08-30 07:34:08.000000000 +0000 @@ -26,7 +26,7 @@ from mapproxy.source import SourceError from mapproxy.client.log import log_request from mapproxy.util.py import reraise_exception -from mapproxy.util.async import run_non_blocking +from mapproxy.util.async_ import run_non_blocking from mapproxy.compat import BytesIO try: diff -Nru mapproxy-1.11.0/mapproxy/source/wms.py mapproxy-1.12.0/mapproxy/source/wms.py --- mapproxy-1.11.0/mapproxy/source/wms.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/source/wms.py 2019-08-30 07:34:08.000000000 +0000 @@ -34,7 +34,8 @@ supports_meta_tiles = True def __init__(self, client, image_opts=None, coverage=None, res_range=None, transparent_color=None, transparent_color_tolerance=None, - supported_srs=None, supported_formats=None, fwd_req_params=None): + supported_srs=None, supported_formats=None, fwd_req_params=None, + error_handler=None): MapLayer.__init__(self, image_opts=image_opts) self.client = client self.supported_srs = supported_srs or [] @@ -51,6 +52,7 @@ self.extent = MapExtent(self.coverage.bbox, self.coverage.srs) else: self.extent = DefaultMapExtent() + self.error_handler = error_handler def is_opaque(self, query): """ @@ -91,6 +93,10 @@ return resp except HTTPClientError as e: + if self.error_handler: + resp = self.error_handler.handle(e.response_code, query) + if resp: + return resp log.warn('could not retrieve WMS map: %s', e) reraise_exception(SourceError(e.args[0]), sys.exc_info()) @@ -103,11 +109,6 @@ if self.supported_srs: if query.srs not in self.supported_srs: return self._get_transformed(query, format) - # some srs are equal but not the same (e.g. 900913/3857) - # use only supported srs so we use the right srs code. - idx = self.supported_srs.index(query.srs) - if self.supported_srs[idx] is not query.srs: - query.srs = self.supported_srs[idx] if self.extent and not self.extent.contains(MapExtent(query.bbox, query.srs)): return self._get_sub_query(query, format) resp = self.client.retrieve(query, format) @@ -123,7 +124,7 @@ def _get_transformed(self, query, format): dst_srs = query.srs - src_srs = self._best_supported_srs(dst_srs) + src_srs = self.supported_srs.best_srs(dst_srs) dst_bbox = query.bbox src_bbox = dst_srs.transform_bbox_to(src_srs, dst_bbox) @@ -150,16 +151,6 @@ img.format = format return img - def _best_supported_srs(self, srs): - latlong = srs.is_latlong - - for srs in self.supported_srs: - if srs.is_latlong == latlong: - return srs - - # else - return self.supported_srs[0] - def _is_compatible(self, other, query): if not isinstance(other, WMSSource): return False @@ -208,11 +199,14 @@ ) class WMSInfoSource(InfoSource): - def __init__(self, client, fi_transformer=None): + def __init__(self, client, fi_transformer=None, coverage=None): self.client = client self.fi_transformer = fi_transformer + self.coverage = coverage def get_info(self, query): + if self.coverage and not self.coverage.contains(query.coord, query.srs): + return None doc = self.client.get_info(query) if self.fi_transformer: doc = self.fi_transformer(doc) diff -Nru mapproxy-1.11.0/mapproxy/srs.py mapproxy-1.12.0/mapproxy/srs.py --- mapproxy-1.11.0/mapproxy/srs.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/srs.py 2019-08-30 07:34:08.000000000 +0000 @@ -63,14 +63,17 @@ class TransformationError(Exception): pass -_proj_initalized = False +_proj_initialized = False def _init_proj(): - global _proj_initalized - if not _proj_initalized and 'proj_data_dir' in base_config().srs: + global _proj_initialized + if not _proj_initialized and 'proj_data_dir' in base_config().srs: proj_data_dir = base_config().srs['proj_data_dir'] + if proj_data_dir is None: + _proj_initialized = True + return log_system.info('loading proj data from %s', proj_data_dir) set_datapath(proj_data_dir) - _proj_initalized = True + _proj_initialized = True _thread_local = threading.local() def SRS(srs_code): @@ -251,24 +254,17 @@ True >>> SRS(4326) == SRS("4326") True - >>> SRS(4326) == SRS(900913) + >>> SRS(4326) == SRS(3857) False - >>> SRS(3857) == SRS(900913) - True - >>> SRS(900913) == SRS(3857) - True - """ if isinstance(other, _SRS): - if (self.srs_code in WEBMERCATOR_EPSG - and other.srs_code in WEBMERCATOR_EPSG): - return True return self.proj.srs == other.proj.srs else: return NotImplemented + def __ne__(self, other): """ - >>> SRS(900913) != SRS(900913) + >>> SRS(3857) != SRS(3857) False >>> SRS(4326) != SRS(900913) True @@ -278,6 +274,7 @@ return NotImplemented else: return not equal_result + def __str__(self): #pylint: disable-msg=E1101 return "SRS %s ('%s')" % (self.srs_code, self.proj.srs) @@ -418,3 +415,41 @@ dst_bbox[1] + (src_bbox[3] - x_y[1]) * (dst_bbox[3]-dst_bbox[1]) / (src_bbox[3] - src_bbox[1])) return func + + +class PreferredSrcSRS(object): + def __init__(self): + self.target_proj = {} + + def add(self, target, prefered_srs): + self.target_proj[target] = prefered_srs + + def preferred_src(self, target, available_src): + if not available_src: + raise ValueError("no available src SRS") + if target in available_src: + return target + if target in self.target_proj: + for preferred in self.target_proj[target]: + if preferred in available_src: + return preferred + + for avail in available_src: + if avail.is_latlong == target.is_latlong: + return avail + return available_src[0] + +class SupportedSRS(object): + def __init__(self, supported_srs, preferred_srs=None): + self.supported_srs = supported_srs + self.preferred_srs = preferred_srs or PreferredSrcSRS() + + def __contains__(self, srs): + return srs in self.supported_srs + + def best_srs(self, target): + return self.preferred_srs.preferred_src(target, self.supported_srs) + + def __eq__(self, other): + # .prefered_srs is set global, so we only compare .supported_srs + return self.supported_srs == other.supported_srs diff -Nru mapproxy-1.11.0/mapproxy/test/conftest.py mapproxy-1.12.0/mapproxy/test/conftest.py --- mapproxy-1.11.0/mapproxy/test/conftest.py 1970-01-01 00:00:00.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/conftest.py 2019-08-30 07:34:08.000000000 +0000 @@ -0,0 +1,7 @@ +def pytest_configure(config): + import sys + sys._called_from_pytest = True + +def pytest_unconfigure(config): + import sys + del sys._called_from_pytest diff -Nru mapproxy-1.11.0/mapproxy/test/helper.py mapproxy-1.12.0/mapproxy/test/helper.py --- mapproxy-1.11.0/mapproxy/test/helper.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/helper.py 2019-08-30 07:34:08.000000000 +0000 @@ -19,17 +19,18 @@ import os import re import sys +from glob import glob as globfunc from contextlib import contextmanager from lxml import etree from mapproxy.test import mocker from mapproxy.compat import string_type, PY2 -from nose.tools import eq_ + class Mocker(object): """ This is a base class for unit-tests that use ``mocker``. This class follows - the nosetest naming conventions for setup and teardown methods. + the xUnit naming conventions for setup and teardown methods. `setup` will initialize a `mocker.Mocker`. The `teardown` method will run ``mocker.verify()``. @@ -140,6 +141,21 @@ match = re.search(regex, value) assert match is not None, '%s ~= %s' % (value, regex) + +def assert_files_in_dir(dir, expected, glob=None): + """ + assert that (only) ``expected`` files are in ``dir``. + ``filter`` can be a globbing patter, other files are ignored if it is set. + """ + if glob is not None: + files = globfunc(os.path.join(dir, glob)) + files = [os.path.basename(f) for f in files] + else: + files = os.listdir(dir) + files.sort() + assert sorted(expected) == files + + def validate_with_dtd(doc, dtd_name, dtd_basedir=None): if dtd_basedir is None: dtd_basedir = os.path.join(os.path.dirname(__file__), 'schemas') @@ -182,7 +198,7 @@ if callable(expected): assert expected(self.xml.xpath(xpath)[0]) else: - eq_(self.xml.xpath(xpath)[0], expected) + assert self.xml.xpath(xpath)[0] == expected def xpath(self, xpath): return self.xml.xpath(xpath) @@ -227,4 +243,5 @@ raise finally: sys.stdout = backup_stdout - sys.stderr = backup_stderr \ No newline at end of file + sys.stderr = backup_stderr + diff -Nru mapproxy-1.11.0/mapproxy/test/http.py mapproxy-1.12.0/mapproxy/test/http.py --- mapproxy-1.11.0/mapproxy/test/http.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/http.py 2019-08-30 07:34:08.000000000 +0000 @@ -64,7 +64,7 @@ self.actual = actual def __str__(self): - return ('requests mismatch, expected:\n' + + return ('requests mismatch (%s), expected:\n' % self.msg + text_indent(str(self.expected), ' ') + '\n got:\n' + text_indent(str(self.actual), ' ')) @@ -268,7 +268,7 @@ def __exit__(self, type, value, traceback): self._thread.shutdown = True - self._thread.join() + self._thread.join(30) if not self._thread.sucess and value: print('requests to mock httpd did not ' @@ -449,7 +449,7 @@ raise finally: t.shutdown = True - t.join(1) + t.join(30) if not t.sucess: raise RequestsMismatchError(t.assertions) @@ -465,7 +465,7 @@ raise finally: t.shutdown = True - t.join(1) + t.join(30) if not t.sucess: raise RequestsMismatchError(t.assertions) diff -Nru mapproxy-1.11.0/mapproxy/test/image.py mapproxy-1.12.0/mapproxy/test/image.py --- mapproxy-1.11.0/mapproxy/test/image.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/image.py 2019-08-30 07:34:08.000000000 +0000 @@ -16,6 +16,10 @@ from __future__ import print_function, division import os +import tempfile + +from io import BytesIO +from contextlib import contextmanager from mapproxy.compat.image import ( Image, @@ -24,17 +28,13 @@ ) from mapproxy.compat import string_type, iteritems -import tempfile -from nose.tools import eq_ -from io import BytesIO -from contextlib import contextmanager def assert_image_mode(img, mode): pos = img.tell() try: img = Image.open(img) - eq_(img.mode, mode) + assert img.mode == mode finally: img.seek(pos) @@ -106,7 +106,6 @@ def create_tmp_image_file(size, two_colored=False): fd, out_file = tempfile.mkstemp(suffix='.png') os.close(fd) - print('creating temp image %s (%r)' % (out_file, size)) img = Image.new('RGBA', size) if two_colored: draw = ImageDraw.Draw(img) diff -Nru mapproxy-1.11.0/mapproxy/test/system/fixture/auth.yaml mapproxy-1.12.0/mapproxy/test/system/fixture/auth.yaml --- mapproxy-1.11.0/mapproxy/test/system/fixture/auth.yaml 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/fixture/auth.yaml 2019-08-30 07:34:08.000000000 +0000 @@ -2,6 +2,9 @@ tms: kml: wmts: + featureinfo_formats: + - mimetype: application/json + suffix: geojson demo: wms: md: @@ -57,11 +60,11 @@ wms_opts: featureinfo: True coverage: - bbox: [179, 89, 180, 89.9] + bbox: [-180, -90, 170, 89.9] bbox_srs: 'EPSG:4326' req: url: http://localhost:42423/service layers: fi source: type: tile - url: http://localhost:42423/%(tms_path)s.png \ No newline at end of file + url: http://localhost:42423/%(tms_path)s.png diff -Nru mapproxy-1.11.0/mapproxy/test/system/fixture/cache_bulk_meta_tiles.yaml mapproxy-1.12.0/mapproxy/test/system/fixture/cache_bulk_meta_tiles.yaml --- mapproxy-1.11.0/mapproxy/test/system/fixture/cache_bulk_meta_tiles.yaml 1970-01-01 00:00:00.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/fixture/cache_bulk_meta_tiles.yaml 2019-08-30 07:34:08.000000000 +0000 @@ -0,0 +1,24 @@ +services: + tms: + wms: + srs: ['EPSG:4326', 'EPSG:4258', 'CRS:84', 'EPSG:900913', 'EPSG:3857', 'EPSG:31466', 'EPSG:31467', 'EPSG:31468', 'EPSG:25831', 'EPSG:25832', 'EPSG:25833'] + md: + title: test + +layers: + - name: bulk + title: bulk + sources: [bulk_cache] + +caches: + bulk_cache: + grids: [GLOBAL_MERCATOR] + meta_size: [2, 2] + bulk_meta_tiles: true + sources: [tms_source] + +sources: + tms_source: + type: tile + url: http://localhost:42423/tiles/%(tc_path)s.png + diff -Nru mapproxy-1.11.0/mapproxy/test/system/fixture/layer.yaml mapproxy-1.12.0/mapproxy/test/system/fixture/layer.yaml --- mapproxy-1.11.0/mapproxy/test/system/fixture/layer.yaml 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/fixture/layer.yaml 2019-08-30 07:34:08.000000000 +0000 @@ -5,6 +5,10 @@ meta_buffer: 0 tile_lock_dir: defaulttilelockdir + srs: + preferred_src_proj: + 'EPSG:25831': ['EPSG:25832', 'EPSG:3857'] + image: # resampling: 'bicubic' paletted: False @@ -19,7 +23,7 @@ kml: wmts: wms: - image_formats: ['image/png', 'image/jpeg', 'png8'] + image_formats: ['image/png', 'image/jpeg', 'png8', 'image/tiff'] srs: ['EPSG:4326', 'CRS:84', 'EPSG:900913', 'EPSG:3857', 'EPSG:31466', 'EPSG:31467', 'EPSG:31468', 'EPSG:25831', 'EPSG:25832', 'EPSG:25833'] bbox_srs: - 'EPSG:3857' @@ -165,6 +169,8 @@ req: url: http://localhost:42423/service layers: bar + wms_opts: + featureinfo: True coverage: bbox: [-180, -80, 170, 80] srs: 'EPSG:4326' @@ -233,3 +239,6 @@ req: url: http://localhost:42423/service layers: fi + coverage: + bbox: [-180,-90,170,80] + srs: 'EPSG:4326' diff -Nru mapproxy-1.11.0/mapproxy/test/system/fixture/watermark.yaml mapproxy-1.12.0/mapproxy/test/system/fixture/watermark.yaml --- mapproxy-1.11.0/mapproxy/test/system/fixture/watermark.yaml 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/fixture/watermark.yaml 2019-08-30 07:34:08.000000000 +0000 @@ -11,11 +11,11 @@ tms: layers: - watermark: + - name: watermark title: Layer with watermark sources: [wms_cache] - watermark_transp: + - name: watermark_transp title: Layer with watermark sources: [wms_transp_cache] diff -Nru mapproxy-1.11.0/mapproxy/test/system/fixture/wmts.yaml mapproxy-1.12.0/mapproxy/test/system/fixture/wmts.yaml --- mapproxy-1.11.0/mapproxy/test/system/fixture/wmts.yaml 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/fixture/wmts.yaml 2019-08-30 07:34:08.000000000 +0000 @@ -10,11 +10,23 @@ tms: kml: wmts: - restful_template: '/myrest/{{Layer}}/{{TileMatrixSet}}/{{TileMatrix}}/{{TileCol}}/{{TileRow}}.{{Format}}' + md: + keyword_list: + - keywords: [wmtskey1, wmtskey2, wmtskey3] + featureinfo_formats: + - mimetype: application/gml+xml; version=3.1 + suffix: gml + - mimetype: application/json + suffix: geojson + + restful_template: '/myrest/{Layer}/{TileMatrixSet}/{TileMatrix}/{TileCol}/{TileRow}.{Format}' + restful_featureinfo_template: '/myrest/{Layer}/{TileMatrixSet}/{TileMatrix}/{TileCol}/{TileRow}/{I}/{J}.{InfoFormat}' wms: md: title: MapProxy test fixture abstract: This is MapProxy. + keyword_list: + - keywords: [wmskey1,wmskey2,wmskey3] online_resource: http://mapproxy.org/ contact: person: Oliver Tonnhofer @@ -39,7 +51,7 @@ sources: [wms_cache_multi] - name: tms_cache title: TMS Cache Layer - sources: [tms_cache] + sources: [tms_cache, wms_fi_only] - name: tms_cache_ul title: TMS Cache Layer sources: [tms_cache_ul] @@ -49,12 +61,14 @@ caches: wms_cache: format: image/jpeg + grids: [GLOBAL_MERCATOR] sources: [wms_cache] wms_cache_multi: format: image/jpeg grids: [CustomGridSet, GoogleMapsCompatible] sources: [wms_cache_130] tms_cache: + grids: [GLOBAL_MERCATOR] sources: [tms_cache] tms_cache_ul: grids: [ulgrid] @@ -66,7 +80,7 @@ sources: wms_cache: type: wms - supported_srs: ['EPSG:900913', 'EPSG:4326'] + supported_srs: ['EPSG:3857', 'EPSG:4326'] wms_opts: featureinfo: True req: @@ -93,6 +107,17 @@ tms_cache: type: tile url: http://localhost:42423/tiles/%(tc_path)s.png + wms_fi_only: + type: wms + wms_opts: + featureinfo: True + map: False + req: + url: http://localhost:42423/service + layers: fi + coverage: + bbox: [-180,-90,170,80] + srs: 'EPSG:4326' grids: gk3: @@ -106,4 +131,4 @@ min_res: 0.703125 ulgrid: base: GLOBAL_MERCATOR - origin: ul \ No newline at end of file + origin: ul diff -Nru mapproxy-1.11.0/mapproxy/test/system/fixture/xslt_featureinfo_input.yaml mapproxy-1.12.0/mapproxy/test/system/fixture/xslt_featureinfo_input.yaml --- mapproxy-1.11.0/mapproxy/test/system/fixture/xslt_featureinfo_input.yaml 1970-01-01 00:00:00.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/fixture/xslt_featureinfo_input.yaml 2019-08-30 07:34:08.000000000 +0000 @@ -0,0 +1,51 @@ +services: + wms: + +layers: + - name: fi_layer + title: Layer with fi source + sources: [fi_wms1] + - name: fi_without_xslt_layer + title: Layer with fi source + sources: [fi_without_xslt] + - name: fi_multi_layer + title: Layer with fi source + sources: [fi_wms1, fi_wms2, fi_wms3] + +sources: + fi_wms1: + type: wms + wms_opts: + version: 1.3.0 + featureinfo: true + featureinfo_xslt: ./fi_in.xsl + featureinfo_format: text/xml + req: + url: http://localhost:42423/service_a + layers: a_one + fi_wms2: + type: wms + wms_opts: + featureinfo: true + featureinfo_xslt: ./fi_in.xsl + featureinfo_format: text/xml + req: + url: http://localhost:42423/service_b + layers: b_one + fi_wms3: + type: wms + wms_opts: + featureinfo: true + featureinfo_xslt: ./fi_in_html.xsl + featureinfo_format: text/html + featureinfo_out_format: text/xml + req: + url: http://localhost:42423/service_d + layers: d_one + fi_without_xslt: + type: wms + wms_opts: + featureinfo: true + req: + url: http://localhost:42423/service_c + layers: c_one diff -Nru mapproxy-1.11.0/mapproxy/test/system/fixture/xslt_featureinfo.yaml mapproxy-1.12.0/mapproxy/test/system/fixture/xslt_featureinfo.yaml --- mapproxy-1.11.0/mapproxy/test/system/fixture/xslt_featureinfo.yaml 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/fixture/xslt_featureinfo.yaml 2019-08-30 07:34:08.000000000 +0000 @@ -41,6 +41,7 @@ featureinfo: true featureinfo_xslt: ./fi_in_html.xsl featureinfo_format: text/html + featureinfo_out_format: text/xml req: url: http://localhost:42423/service_d layers: d_one @@ -50,4 +51,4 @@ featureinfo: true req: url: http://localhost:42423/service_c - layers: c_one \ No newline at end of file + layers: c_one diff -Nru mapproxy-1.11.0/mapproxy/test/system/__init__.py mapproxy-1.12.0/mapproxy/test/system/__init__.py --- mapproxy-1.11.0/mapproxy/test/system/__init__.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/__init__.py 2019-08-30 07:34:08.000000000 +0000 @@ -14,78 +14,82 @@ # limitations under the License. from __future__ import division + import os -import tempfile import shutil -from webtest import TestApp as TestApp_ + +import pytest + +from webtest import TestApp as _TestApp + from mapproxy.wsgiapp import make_wsgi_app -class TestApp(TestApp_): + +class WSGITestApp(_TestApp): """ Wraps webtest.TestApp and explicitly converts URLs to strings. Behavior changed with webtest from 1.2->1.3. """ + def get(self, url, *args, **kw): - return TestApp_.get(self, str(url), *args, **kw) + return _TestApp.get(self, str(url), *args, **kw) + + +@pytest.mark.usefixtures("cache_dir") +class SysTest(object): + """ + Baseclass for pytest-based system tests. + Provides `app` fixture with a configured MapProxy instance, wrapped in + webtest.TestApp. + + `app` is reused within each test class, `cache_dir` is cleaned with + each test. + """ -def module_setup(test_config, config_file, with_cache_data=False): - prepare_env(test_config, config_file, with_cache_data) - create_app(test_config) - -def prepare_env(test_config, config_file, with_cache_data=False): - if 'fixture_dir' not in test_config: - test_config['fixture_dir'] = os.path.join(os.path.dirname(__file__), 'fixture') - - fixture_layer_conf = os.path.join(test_config['fixture_dir'], config_file) - - if 'base_dir' not in test_config: - test_config['tmp_dir'] = tempfile.mkdtemp() - test_config['base_dir'] = os.path.join(test_config['tmp_dir'], 'etc') - os.mkdir(test_config['base_dir']) - test_config['config_file'] = os.path.join(test_config['base_dir'], config_file) - test_config['cache_dir'] = os.path.join(test_config['base_dir'], 'cache_data') - shutil.copy(fixture_layer_conf, test_config['config_file']) - if with_cache_data: - shutil.copytree(os.path.join(test_config['fixture_dir'], 'cache_data'), - test_config['cache_dir']) - -def create_app(test_config): - app = make_wsgi_app(test_config['config_file'], ignore_config_warnings=False) - app.base_config.debug_mode = True - test_config['app'] = TestApp(app, use_unicode=False) - -def module_teardown(test_config): - shutil.rmtree(test_config['base_dir']) - if 'tmp_dir' in test_config: - shutil.rmtree(test_config['tmp_dir']) - - test_config.clear() - -def make_base_config(test_config): - def wrapped(): - if hasattr(test_config['app'], 'base_config'): - return test_config['app'].base_config - return test_config['app'].app.base_config - return wrapped - -class SystemTest(object): - def setup(self): - self.app = self.config['app'] - self.created_tiles = [] - self.base_config = make_base_config(self.config) - - def created_tiles_filenames(self): - base_dir = self.base_config().cache.base_dir - for filename in self.created_tiles: - yield os.path.join(base_dir, filename) - - def _test_created_tiles(self): - for filename in self.created_tiles_filenames(): - if not os.path.exists(filename): - assert False, "didn't found tile " + filename - - def teardown(self): - self._test_created_tiles() - for filename in self.created_tiles_filenames(): - if os.path.exists(filename): - os.remove(filename) + @pytest.fixture(scope="class") + def app(self, base_dir, config_file, additional_files): + filename = base_dir.join(config_file) + app = make_wsgi_app(filename.strpath, ignore_config_warnings=False) + app.base_config.debug_mode = True + return WSGITestApp(app, use_unicode=False) + + @pytest.fixture(scope="class") + def additional_files(self, base_dir): + return + + @pytest.fixture(scope="class") + def base_config(self, app): + return app.app.base_config + + @pytest.fixture(scope="class") + def base_dir(self, tmpdir_factory, config_file): + dir = tmpdir_factory.mktemp("base_dir") + + fixture_dir = os.path.join(os.path.dirname(__file__), "fixture") + fixture_layer_conf = os.path.join(fixture_dir, config_file) + shutil.copy(fixture_layer_conf, dir.strpath) + + return dir + + @pytest.fixture(scope="function") + def cache_dir(self, base_dir): + """ + cache_dir fixture returns a fresh cache_data directory used by `app`. + """ + cache_dir = base_dir.join("cache_data") + if cache_dir.check(): + cache_dir.remove() + + yield cache_dir + + if cache_dir.check(): + cache_dir.remove() + + @pytest.fixture(scope="function") + def fixture_cache_data(self, cache_dir): + """ + fixture_cache_data ensures that the system/fixture files are copied into + `cache_dir`. + """ + fixture_dir = os.path.join(os.path.dirname(__file__), "fixture") + shutil.copytree(os.path.join(fixture_dir, "cache_data"), cache_dir.strpath) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_arcgis.py mapproxy-1.12.0/mapproxy/test/system/test_arcgis.py --- mapproxy-1.11.0/mapproxy/test/system/test_arcgis.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_arcgis.py 2019-08-30 07:34:08.000000000 +0000 @@ -16,96 +16,141 @@ from __future__ import division from io import BytesIO + +import pytest + from mapproxy.request.wms import WMS111FeatureInfoRequest from mapproxy.test.image import is_png, create_tmp_image from mapproxy.test.http import mock_httpd -from mapproxy.test.system import module_setup, module_teardown, SystemTest -from nose.tools import eq_ +from mapproxy.test.system import SysTest + -test_config = {} +@pytest.fixture(scope="module") +def config_file(): + return "arcgis.yaml" -def setup_module(): - module_setup(test_config, 'arcgis.yaml') -def teardown_module(): - module_teardown(test_config) +transp = create_tmp_image((512, 512), mode="RGBA", color=(0, 0, 0, 0)) -transp = create_tmp_image((512, 512), mode='RGBA', color=(0, 0, 0, 0)) +class TestArcgisSource(SysTest): -class TestArcgisSource(SystemTest): - config = test_config def setup(self): - SystemTest.setup(self) - self.common_fi_req = WMS111FeatureInfoRequest(url='/service?', - param=dict(x='10', y='20', width='200', height='200', layers='app2_with_layers_fi_layer', - format='image/png', query_layers='app2_with_layers_fi_layer', styles='', - bbox='1000,400,2000,1400', srs='EPSG:3857', info_format='application/json')) - - def test_get_tile(self): - expected_req = [({'path': '/arcgis/rest/services/ExampleLayer/ImageServer/exportImage?f=image&format=png&imageSR=900913&bboxSR=900913&bbox=-20037508.342789244,-20037508.342789244,20037508.342789244,20037508.342789244&size=512,512'}, - {'body': transp, 'headers': {'content-type': 'image/png'}}), - ] - - with mock_httpd(('localhost', 42423), expected_req, bbox_aware_query_comparator=True): - resp = self.app.get('/tms/1.0.0/app2_layer/0/0/1.png') - eq_(resp.content_type, 'image/png') - eq_(resp.content_length, len(resp.body)) + self.common_fi_req = WMS111FeatureInfoRequest( + url="/service?", + param=dict( + x="10", + y="20", + width="200", + height="200", + layers="app2_with_layers_fi_layer", + format="image/png", + query_layers="app2_with_layers_fi_layer", + styles="", + bbox="1000,400,2000,1400", + srs="EPSG:3857", + info_format="application/json", + ), + ) + + def test_get_tile(self, app): + expected_req = [ + ( + { + "path": "/arcgis/rest/services/ExampleLayer/ImageServer/exportImage?f=image&format=png&imageSR=900913&bboxSR=900913&bbox=-20037508.342789244,-20037508.342789244,20037508.342789244,20037508.342789244&size=512,512" + }, + {"body": transp, "headers": {"content-type": "image/png"}}, + ) + ] + + with mock_httpd( + ("localhost", 42423), expected_req, bbox_aware_query_comparator=True + ): + resp = app.get("/tms/1.0.0/app2_layer/0/0/1.png") + assert resp.content_type == "image/png" + assert resp.content_length == len(resp.body) data = BytesIO(resp.body) assert is_png(data) - def test_get_tile_with_layer(self): - expected_req = [({'path': '/arcgis/rest/services/ExampleLayer/MapServer/export?f=image&format=png&layers=show:0,1&imageSR=900913&bboxSR=900913&bbox=-20037508.342789244,-20037508.342789244,20037508.342789244,20037508.342789244&size=512,512'}, - {'body': transp, 'headers': {'content-type': 'image/png'}}), - ] - - with mock_httpd(('localhost', 42423), expected_req, bbox_aware_query_comparator=True): - resp = self.app.get('/tms/1.0.0/app2_with_layers_layer/0/0/1.png') - eq_(resp.content_type, 'image/png') - eq_(resp.content_length, len(resp.body)) + def test_get_tile_with_layer(self, app): + expected_req = [ + ( + { + "path": "/arcgis/rest/services/ExampleLayer/MapServer/export?f=image&format=png&layers=show:0,1&imageSR=900913&bboxSR=900913&bbox=-20037508.342789244,-20037508.342789244,20037508.342789244,20037508.342789244&size=512,512" + }, + {"body": transp, "headers": {"content-type": "image/png"}}, + ) + ] + + with mock_httpd( + ("localhost", 42423), expected_req, bbox_aware_query_comparator=True + ): + resp = app.get("/tms/1.0.0/app2_with_layers_layer/0/0/1.png") + assert resp.content_type == "image/png" + assert resp.content_length == len(resp.body) data = BytesIO(resp.body) assert is_png(data) - def test_get_tile_from_missing_arcgis_layer(self): - expected_req = [({'path': '/arcgis/rest/services/NonExistentLayer/ImageServer/exportImage?f=image&format=png&imageSR=900913&bboxSR=900913&bbox=-20037508.342789244,-20037508.342789244,20037508.342789244,20037508.342789244&size=512,512'}, - {'body': b'', 'status': 400}), - ] - - with mock_httpd(('localhost', 42423), expected_req, bbox_aware_query_comparator=True): - resp = self.app.get('/tms/1.0.0/app2_wrong_url_layer/0/0/1.png', status=500) - eq_(resp.status_code, 500) - - def test_identify(self): - expected_req = [( - {'path': '/arcgis/rest/services/ExampleLayer/MapServer/identify?f=json&' - 'geometry=1050.000000,1300.000000&returnGeometry=true&imageDisplay=200,200,96' - '&mapExtent=1000.0,400.0,2000.0,1400.0&layers=show:1,2,3' - '&tolerance=10&geometryType=esriGeometryPoint&sr=3857' - }, - {'body': b'{"results": []}', 'headers': {'content-type': 'application/json'}}), + def test_get_tile_from_missing_arcgis_layer(self, app): + expected_req = [ + ( + { + "path": "/arcgis/rest/services/NonExistentLayer/ImageServer/exportImage?f=image&format=png&imageSR=900913&bboxSR=900913&bbox=-20037508.342789244,-20037508.342789244,20037508.342789244,20037508.342789244&size=512,512" + }, + {"body": b"", "status": 400}, + ) ] - with mock_httpd(('localhost', 42423), expected_req, bbox_aware_query_comparator=True): - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'application/json') - eq_(resp.content_length, len(resp.body)) - eq_(resp.body, b'{"results": []}') - + with mock_httpd( + ("localhost", 42423), expected_req, bbox_aware_query_comparator=True + ): + resp = app.get("/tms/1.0.0/app2_wrong_url_layer/0/0/1.png", status=500) + assert resp.status_code == 500 + + def test_identify(self, app): + expected_req = [ + ( + { + "path": "/arcgis/rest/services/ExampleLayer/MapServer/identify?f=json&" + "geometry=1050.000000,1300.000000&returnGeometry=true&imageDisplay=200,200,96" + "&mapExtent=1000.0,400.0,2000.0,1400.0&layers=show:1,2,3" + "&tolerance=10&geometryType=esriGeometryPoint&sr=3857" + }, + { + "body": b'{"results": []}', + "headers": {"content-type": "application/json"}, + }, + ) + ] - def test_transformed_identify(self): - expected_req = [( - {'path': '/arcgis/rest/services/ExampleLayer/MapServer/identify?f=json&' - 'geometry=573295.377585,6927820.884193&returnGeometry=true&imageDisplay=200,321,96' - '&mapExtent=556597.453966,6446275.84102,890555.926346,6982997.92039&layers=show:1,2,3' - '&tolerance=10&geometryType=esriGeometryPoint&sr=3857' - }, - {'body': b'{"results": []}', 'headers': {'content-type': 'application/json'}}), + with mock_httpd( + ("localhost", 42423), expected_req, bbox_aware_query_comparator=True + ): + resp = app.get(self.common_fi_req) + assert resp.content_type == "application/json" + assert resp.content_length == len(resp.body) + assert resp.body == b'{"results": []}' + + def test_transformed_identify(self, app): + expected_req = [ + ( + { + "path": "/arcgis/rest/services/ExampleLayer/MapServer/identify?f=json&" + "geometry=573295.377585,6927820.884193&returnGeometry=true&imageDisplay=200,321,96" + "&mapExtent=556597.453966,6446275.84102,890555.926346,6982997.92039&layers=show:1,2,3" + "&tolerance=10&geometryType=esriGeometryPoint&sr=3857" + }, + { + "body": b'{"results": []}', + "headers": {"content-type": "application/json"}, + }, + ) ] - with mock_httpd(('localhost', 42423), expected_req): - self.common_fi_req.params.bbox = '5,50,8,53' - self.common_fi_req.params.srs = 'EPSG:4326' - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'application/json') - eq_(resp.content_length, len(resp.body)) - eq_(resp.body, b'{"results": []}') + with mock_httpd(("localhost", 42423), expected_req): + self.common_fi_req.params.bbox = "5,50,8,53" + self.common_fi_req.params.srs = "EPSG:4326" + resp = app.get(self.common_fi_req) + assert resp.content_type == "application/json" + assert resp.content_length == len(resp.body) + assert resp.body == b'{"results": []}' diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_auth.py mapproxy-1.12.0/mapproxy/test/system/test_auth.py --- mapproxy-1.11.0/mapproxy/test/system/test_auth.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_auth.py 2019-08-30 07:34:08.000000000 +0000 @@ -15,814 +15,1120 @@ from __future__ import division -from mapproxy.test.system import module_setup, module_teardown, SystemTest -from mapproxy.test.image import img_from_buf, create_tmp_image, is_transparent -from mapproxy.test.http import MockServ -from nose.tools import eq_ -from mapproxy.util.geom import geom_support +import pytest + from mapproxy.srs import bbox_equals +from mapproxy.util.geom import geom_support +from mapproxy.test.http import MockServ +from mapproxy.test.image import img_from_buf, create_tmp_image, is_transparent +from mapproxy.test.system import SysTest -test_config = {} -def setup_module(): - module_setup(test_config, 'auth.yaml') +@pytest.fixture(scope="module") +def config_file(): + return "auth.yaml" -def teardown_module(): - module_teardown(test_config) -TESTSERVER_ADDRESS = 'localhost', 42423 +TESTSERVER_ADDRESS = "localhost", 42423 CAPABILITIES_REQ = "/service?request=GetCapabilities&service=WMS&Version=1.1.1" -MAP_REQ = ("/service?request=GetMap&service=WMS&Version=1.1.1&SRS=EPSG:4326" - "&BBOX=-80,-40,0,0&WIDTH=200&HEIGHT=100&styles=&FORMAT=image/png&") -FI_REQ = ("/service?request=GetFeatureInfo&service=WMS&Version=1.1.1&SRS=EPSG:4326" - "&BBOX=-80,-40,0,0&WIDTH=200&HEIGHT=100&styles=&FORMAT=image/png&X=10&Y=10&") - -if not geom_support: - from nose.plugins.skip import SkipTest - raise SkipTest('requires Shapely') +MAP_REQ = ( + "/service?request=GetMap&service=WMS&Version=1.1.1&SRS=EPSG:4326" + "&BBOX=-80,-40,0,0&WIDTH=200&HEIGHT=100&styles=&FORMAT=image/png&" +) +FI_REQ = ( + "/service?request=GetFeatureInfo&service=WMS&Version=1.1.1&SRS=EPSG:4326" + "&BBOX=-80,-40,0,0&WIDTH=200&HEIGHT=100&styles=&FORMAT=image/png&X=10&Y=10&" +) -class TestWMSAuth(SystemTest): - config = test_config + +pytestmark = pytest.mark.skipif(not geom_support, reason="Shapely required") + + +class TestWMSAuth(SysTest): # ### # see mapproxy.test.unit.test_auth for WMS GetMap request tests # ### - def test_capabilities_authorize_all(self): + def test_capabilities_authorize_all(self, app): + def auth(service, layers, **kw): - eq_(service, 'wms.capabilities') - eq_(len(layers), 8) - return {'authorized': 'full'} + assert service == "wms.capabilities" + assert len(layers) == 8 + return {"authorized": "full"} - resp = self.app.get(CAPABILITIES_REQ, extra_environ={'mapproxy.authorize': auth}) + resp = app.get(CAPABILITIES_REQ, extra_environ={"mapproxy.authorize": auth}) xml = resp.lxml - eq_(xml.xpath('//Layer/Name/text()'), ['layer1', 'layer1a', 'layer1b', 'layer2', 'layer2a', 'layer2b', 'layer2b1', 'layer3']) + assert xml.xpath("//Layer/Name/text()") == [ + "layer1", + "layer1a", + "layer1b", + "layer2", + "layer2a", + "layer2b", + "layer2b1", + "layer3", + ] + + def test_capabilities_authorize_none(self, app): - def test_capabilities_authorize_none(self): def auth(service, layers, **kw): - eq_(service, 'wms.capabilities') - eq_(len(layers), 8) - return {'authorized': 'none'} - self.app.get(CAPABILITIES_REQ, extra_environ={'mapproxy.authorize': auth}, status=403) + assert service == "wms.capabilities" + assert len(layers) == 8 + return {"authorized": "none"} + + app.get( + CAPABILITIES_REQ, extra_environ={"mapproxy.authorize": auth}, status=403 + ) + + def test_capabilities_unauthenticated(self, app): - def test_capabilities_unauthenticated(self): def auth(service, layers, **kw): - eq_(service, 'wms.capabilities') - eq_(len(layers), 8) - return {'authorized': 'unauthenticated'} - self.app.get(CAPABILITIES_REQ, extra_environ={'mapproxy.authorize': auth}, status=401) + assert service == "wms.capabilities" + assert len(layers) == 8 + return {"authorized": "unauthenticated"} + + app.get( + CAPABILITIES_REQ, extra_environ={"mapproxy.authorize": auth}, status=401 + ) + + def test_capabilities_authorize_partial(self, app): - def test_capabilities_authorize_partial(self): def auth(service, layers, **kw): - eq_(service, 'wms.capabilities') - eq_(len(layers), 8) + assert service == "wms.capabilities" + assert len(layers) == 8 return { - 'authorized': 'partial', - 'layers': { - 'layer1a': {'map': True}, - 'layer2': {'map': True}, - 'layer2b': {'map': True}, - 'layer2b1': {'map': True}, - } + "authorized": "partial", + "layers": { + "layer1a": {"map": True}, + "layer2": {"map": True}, + "layer2b": {"map": True}, + "layer2b1": {"map": True}, + }, } - resp = self.app.get(CAPABILITIES_REQ, extra_environ={'mapproxy.authorize': auth}) + + resp = app.get(CAPABILITIES_REQ, extra_environ={"mapproxy.authorize": auth}) xml = resp.lxml # layer1a not included cause root layer (layer1) is not permitted - eq_(xml.xpath('//Layer/Name/text()'), ['layer2', 'layer2b', 'layer2b1']) + assert xml.xpath("//Layer/Name/text()") == ["layer2", "layer2b", "layer2b1"] + + def test_capabilities_authorize_partial_limited_to(self, app): - def test_capabilities_authorize_partial_limited_to(self): def auth(service, layers, **kw): - eq_(service, 'wms.capabilities') - eq_(len(layers), 8) + assert service == "wms.capabilities" + assert len(layers) == 8 return { - 'authorized': 'partial', - 'layers': { - 'layer1a': {'map': True}, - 'layer2': {'map': True, 'limited_to': {'srs': 'EPSG:4326', 'geometry': [-40.0, -50.0, 0.0, 5.0]}}, - 'layer2b': {'map': True}, - 'layer2b1': {'map': True}, - } + "authorized": "partial", + "layers": { + "layer1a": {"map": True}, + "layer2": { + "map": True, + "limited_to": { + "srs": "EPSG:4326", + "geometry": [-40.0, -50.0, 0.0, 5.0], + }, + }, + "layer2b": {"map": True}, + "layer2b1": {"map": True}, + }, } - resp = self.app.get(CAPABILITIES_REQ, extra_environ={'mapproxy.authorize': auth}) + + resp = app.get(CAPABILITIES_REQ, extra_environ={"mapproxy.authorize": auth}) xml = resp.lxml # layer1a not included cause root layer (layer1) is not permitted - eq_(xml.xpath('//Layer/Name/text()'), ['layer2', 'layer2b', 'layer2b1']) - limited_bbox = xml.xpath('//Layer/LatLonBoundingBox')[1] - eq_(float(limited_bbox.attrib['minx']), -40.0) - eq_(float(limited_bbox.attrib['miny']), -50.0) - eq_(float(limited_bbox.attrib['maxx']), 0.0) - eq_(float(limited_bbox.attrib['maxy']), 5.0) - - def test_capabilities_authorize_partial_global_limited(self): - def auth(service, layers, **kw): - eq_(service, 'wms.capabilities') - eq_(len(layers), 8) - return { - 'authorized': 'partial', - 'limited_to': {'srs': 'EPSG:4326', 'geometry': [-40.0, -50.0, 0.0, 5.0]}, - 'layers': { - 'layer1': {'map': True}, - 'layer1a': {'map': True}, - 'layer2': {'map': True}, - 'layer2b': {'map': True}, - 'layer2b1': {'map': True}, - } + assert xml.xpath("//Layer/Name/text()") == ["layer2", "layer2b", "layer2b1"] + limited_bbox = xml.xpath("//Layer/LatLonBoundingBox")[1] + assert float(limited_bbox.attrib["minx"]) == -40.0 + assert float(limited_bbox.attrib["miny"]) == -50.0 + assert float(limited_bbox.attrib["maxx"]) == 0.0 + assert float(limited_bbox.attrib["maxy"]) == 5.0 + + def test_capabilities_authorize_partial_global_limited(self, app): + + def auth(service, layers, **kw): + assert service == "wms.capabilities" + assert len(layers) == 8 + return { + "authorized": "partial", + "limited_to": { + "srs": "EPSG:4326", + "geometry": [171.0, -50.0, 178.0, 5.0], + }, + "layers": { + "layer1": {"map": True}, + "layer1a": {"map": True}, + "layer2": {"map": True}, + "layer2b": {"map": True}, + "layer2b1": {"map": True}, + }, } - resp = self.app.get(CAPABILITIES_REQ, extra_environ={'mapproxy.authorize': auth}) + + resp = app.get(CAPABILITIES_REQ, extra_environ={"mapproxy.authorize": auth}) xml = resp.lxml # print resp.body # layer2/2b/2b1 not included because coverage of 2b1 is outside of global limited_to - eq_(xml.xpath('//Layer/Name/text()'), ['layer1', 'layer1a']) - limited_bbox = xml.xpath('//Layer/LatLonBoundingBox')[1] - eq_(float(limited_bbox.attrib['minx']), -40.0) - eq_(float(limited_bbox.attrib['miny']), -50.0) - eq_(float(limited_bbox.attrib['maxx']), 0.0) - eq_(float(limited_bbox.attrib['maxy']), 5.0) - - def test_capabilities_authorize_partial_with_fi(self): - def auth(service, layers, **kw): - eq_(service, 'wms.capabilities') - eq_(len(layers), 8) - return { - 'authorized': 'partial', - 'layers': { - 'layer1': {'map': True}, - 'layer1a': {'map': True}, - 'layer2': {'map': True, 'featureinfo': True}, - 'layer2b': {'map': True, 'featureinfo': True}, - 'layer2b1': {'map': True, 'featureinfo': True}, - } + assert xml.xpath("//Layer/Name/text()") == ["layer1", "layer1a"] + limited_bbox = xml.xpath("//Layer/LatLonBoundingBox")[1] + assert float(limited_bbox.attrib["minx"]) == 171.0 + assert float(limited_bbox.attrib["miny"]) == -50.0 + assert float(limited_bbox.attrib["maxx"]) == 178.0 + assert float(limited_bbox.attrib["maxy"]) == 5.0 + + def test_capabilities_authorize_partial_with_fi(self, app): + + def auth(service, layers, **kw): + assert service == "wms.capabilities" + assert len(layers) == 8 + return { + "authorized": "partial", + "layers": { + "layer1": {"map": True}, + "layer1a": {"map": True}, + "layer2": {"map": True, "featureinfo": True}, + "layer2b": {"map": True, "featureinfo": True}, + "layer2b1": {"map": True, "featureinfo": True}, + }, } - resp = self.app.get(CAPABILITIES_REQ, extra_environ={'mapproxy.authorize': auth}) + + resp = app.get(CAPABILITIES_REQ, extra_environ={"mapproxy.authorize": auth}) xml = resp.lxml - eq_(xml.xpath('//Layer/Name/text()'), ['layer1', 'layer1a', 'layer2', 'layer2b', 'layer2b1']) - layers = xml.xpath('//Layer') - assert layers[3][0].text == 'layer2' - assert layers[3].attrib['queryable'] == '1' - assert layers[4][0].text == 'layer2b' - assert layers[4].attrib['queryable'] == '1' - assert layers[5][0].text == 'layer2b1' - assert layers[5].attrib['queryable'] == '1' + assert xml.xpath("//Layer/Name/text()") == [ + "layer1", + "layer1a", + "layer2", + "layer2b", + "layer2b1", + ] + layers = xml.xpath("//Layer") + assert layers[3][0].text == "layer2" + assert layers[3].attrib["queryable"] == "1" + assert layers[4][0].text == "layer2b" + assert layers[4].attrib["queryable"] == "1" + assert layers[5][0].text == "layer2b1" + assert layers[5].attrib["queryable"] == "1" + + def test_get_map_authorized(self, app): - def test_get_map_authorized(self): def auth(service, layers, query_extent, **kw): - eq_(query_extent, ('EPSG:4326', (-80.0, -40.0, 0.0, 0.0))) - eq_(service, 'wms.map') - eq_(len(layers), 1) - return { - 'authorized': 'partial', - 'layers': { - 'layer1': {'map': True}, - } - } - resp = self.app.get(MAP_REQ + 'layers=layer1', extra_environ={'mapproxy.authorize': auth}) - eq_(resp.content_type, 'image/png') + assert query_extent == ("EPSG:4326", (-80.0, -40.0, 0.0, 0.0)) + assert service == "wms.map" + assert len(layers) == 1 + return {"authorized": "partial", "layers": {"layer1": {"map": True}}} + + resp = app.get( + MAP_REQ + "layers=layer1", extra_environ={"mapproxy.authorize": auth} + ) + assert resp.content_type == "image/png" + + def test_get_map_authorized_limited(self, app): - def test_get_map_authorized_limited(self): def auth(service, layers, query_extent, **kw): - eq_(query_extent, ('EPSG:4326', (-80.0, -40.0, 0.0, 0.0))) - eq_(service, 'wms.map') - eq_(len(layers), 1) - return { - 'authorized': 'partial', - 'layers': { - 'layer1': { - 'map': True, - 'limited_to': {'srs': 'EPSG:4326', 'geometry': [-40.0, -40.0, 0.0, 0.0]}, - }, - } + assert query_extent == ("EPSG:4326", (-80.0, -40.0, 0.0, 0.0)) + assert service == "wms.map" + assert len(layers) == 1 + return { + "authorized": "partial", + "layers": { + "layer1": { + "map": True, + "limited_to": { + "srs": "EPSG:4326", + "geometry": [-40.0, -40.0, 0.0, 0.0], + }, + } + }, } - resp = self.app.get(MAP_REQ + 'layers=layer1', extra_environ={'mapproxy.authorize': auth}) - eq_(resp.content_type, 'image/png') + + resp = app.get( + MAP_REQ + "layers=layer1", extra_environ={"mapproxy.authorize": auth} + ) + assert resp.content_type == "image/png" img = img_from_buf(resp.body) # left part not authorized, only bgcolor assert len(img.crop((0, 0, 100, 100)).getcolors()) == 1 # right part authorized, bgcolor + text assert len(img.crop((100, 0, 200, 100)).getcolors()) >= 2 - def test_get_map_authorized_global_limited(self): + def test_get_map_authorized_global_limited(self, app): + def auth(service, layers, query_extent, **kw): - eq_(query_extent, ('EPSG:4326', (-80.0, -40.0, 0.0, 0.0))) - eq_(service, 'wms.map') - eq_(len(layers), 1) - return { - 'authorized': 'partial', - 'limited_to': {'srs': 'EPSG:4326', 'geometry': [-20.0, -40.0, 0.0, 0.0]}, - 'layers': { - 'layer1': { - 'map': True, - 'limited_to': {'srs': 'EPSG:4326', 'geometry': [-40.0, -40.0, 0.0, 0.0]}, - }, - } + assert query_extent == ("EPSG:4326", (-80.0, -40.0, 0.0, 0.0)) + assert service == "wms.map" + assert len(layers) == 1 + return { + "authorized": "partial", + "limited_to": { + "srs": "EPSG:4326", + "geometry": [-20.0, -40.0, 0.0, 0.0], + }, + "layers": { + "layer1": { + "map": True, + "limited_to": { + "srs": "EPSG:4326", + "geometry": [-40.0, -40.0, 0.0, 0.0], + }, + } + }, } - resp = self.app.get(MAP_REQ + 'layers=layer1', extra_environ={'mapproxy.authorize': auth}) - eq_(resp.content_type, 'image/png') + + resp = app.get( + MAP_REQ + "layers=layer1", extra_environ={"mapproxy.authorize": auth} + ) + assert resp.content_type == "image/png" img = img_from_buf(resp.body) # left part not authorized, only bgcolor assert len(img.crop((0, 0, 100, 100)).getcolors()) == 1 # right part authorized, bgcolor + text assert len(img.crop((100, 0, 200, 100)).getcolors()) >= 2 - def test_get_map_authorized_none(self): + def test_get_map_authorized_none(self, app): + def auth(service, layers, query_extent, **kw): - eq_(query_extent, ('EPSG:4326', (-80.0, -40.0, 0.0, 0.0))) - eq_(service, 'wms.map') - eq_(len(layers), 1) - return { - 'authorized': 'partial', - 'layers': { - 'layer1': {'map': False}, - } - } - self.app.get(MAP_REQ + 'layers=layer1', extra_environ={'mapproxy.authorize': auth}, status=403) + assert query_extent == ("EPSG:4326", (-80.0, -40.0, 0.0, 0.0)) + assert service == "wms.map" + assert len(layers) == 1 + return {"authorized": "partial", "layers": {"layer1": {"map": False}}} + + app.get( + MAP_REQ + "layers=layer1", + extra_environ={"mapproxy.authorize": auth}, + status=403, + ) + + def test_get_featureinfo_limited_to_inside(self, app): - def test_get_featureinfo_limited_to_inside(self): def auth(service, layers, query_extent, **kw): - eq_(query_extent, ('EPSG:4326', (-80.0, -40.0, 0.0, 0.0))) - eq_(service, 'wms.featureinfo') - eq_(len(layers), 1) - return { - 'authorized': 'partial', - 'layers': { - 'layer1b': {'featureinfo': True, 'limited_to': {'srs': 'EPSG:4326', 'geometry': [-80.0, -40.0, 0.0, 0.0]}}, - } + assert query_extent == ("EPSG:4326", (-80.0, -40.0, 0.0, 0.0)) + assert service == "wms.featureinfo" + assert len(layers) == 1 + return { + "authorized": "partial", + "layers": { + "layer1b": { + "featureinfo": True, + "limited_to": { + "srs": "EPSG:4326", + "geometry": [-80.0, -40.0, 0.0, 0.0], + }, + } + }, } + serv = MockServ(port=42423) - serv.expects('/service?request=GetFeatureInfo&service=WMS&Version=1.1.1&SRS=EPSG:4326' - '&BBOX=-80.0,-40.0,0.0,0.0&WIDTH=200&HEIGHT=100&styles=&FORMAT=image/png&X=10&Y=10' - '&query_layers=fi&layers=fi') - serv.returns(b'infoinfo') + serv.expects( + "/service?request=GetFeatureInfo&service=WMS&Version=1.1.1&SRS=EPSG:4326" + "&BBOX=-80.0,-40.0,0.0,0.0&WIDTH=200&HEIGHT=100&styles=&FORMAT=image/png&X=10&Y=10" + "&query_layers=fi&layers=fi" + ) + serv.returns(b"infoinfo") with serv: - resp = self.app.get(FI_REQ + 'query_layers=layer1b&layers=layer1b', extra_environ={'mapproxy.authorize': auth}) - eq_(resp.body, b'infoinfo') + resp = app.get( + FI_REQ + "query_layers=layer1b&layers=layer1b", + extra_environ={"mapproxy.authorize": auth}, + ) + assert resp.body == b"infoinfo" + + def test_get_featureinfo_limited_to_outside(self, app): - def test_get_featureinfo_limited_to_outside(self): def auth(service, layers, query_extent, **kw): - eq_(query_extent, ('EPSG:4326', (-80.0, -40.0, 0.0, 0.0))) - eq_(service, 'wms.featureinfo') - eq_(len(layers), 1) - return { - 'authorized': 'partial', - 'layers': { - 'layer1b': {'featureinfo': True, 'limited_to': {'srs': 'EPSG:4326', 'geometry': [-80.0, -40.0, 0.0, -10.0]}}, - } + assert query_extent == ("EPSG:4326", (-80.0, -40.0, 0.0, 0.0)) + assert service == "wms.featureinfo" + assert len(layers) == 1 + return { + "authorized": "partial", + "layers": { + "layer1b": { + "featureinfo": True, + "limited_to": { + "srs": "EPSG:4326", + "geometry": [-80.0, -40.0, 0.0, -10.0], + }, + } + }, } - resp = self.app.get(FI_REQ + 'query_layers=layer1b&layers=layer1b', extra_environ={'mapproxy.authorize': auth}) + resp = app.get( + FI_REQ + "query_layers=layer1b&layers=layer1b", + extra_environ={"mapproxy.authorize": auth}, + ) # empty response, FI request is outside of limited_to geometry - eq_(resp.body, b'') + assert resp.body == b"" + + def test_get_featureinfo_global_limited(self, app): - def test_get_featureinfo_global_limited(self): def auth(service, layers, query_extent, **kw): - eq_(query_extent, ('EPSG:4326', (-80.0, -40.0, 0.0, 0.0))) - eq_(service, 'wms.featureinfo') - eq_(len(layers), 1) - return { - 'authorized': 'partial', - 'limited_to': {'srs': 'EPSG:4326', 'geometry': [-40.0, -40.0, 0.0, 0.0]}, - 'layers': { - 'layer1b': {'featureinfo': True}, + assert query_extent == ("EPSG:4326", (-80.0, -40.0, 0.0, 0.0)) + assert service == "wms.featureinfo" + assert len(layers) == 1 + return { + "authorized": "partial", + "limited_to": { + "srs": "EPSG:4326", + "geometry": [-40.0, -40.0, 0.0, 0.0], }, + "layers": {"layer1b": {"featureinfo": True}}, } - resp = self.app.get(FI_REQ + 'query_layers=layer1b&layers=layer1b', extra_environ={'mapproxy.authorize': auth}) + + resp = app.get( + FI_REQ + "query_layers=layer1b&layers=layer1b", + extra_environ={"mapproxy.authorize": auth}, + ) # empty response, FI request is outside of limited_to geometry - eq_(resp.body, b'') + assert resp.body == b"" + + +TMS_CAPABILITIES_REQ = "/tms/1.0.0" -TMS_CAPABILITIES_REQ = '/tms/1.0.0' +class TestTMSAuth(SysTest): -class TestTMSAuth(SystemTest): - config = test_config + def test_capabilities_authorize_all(self, app): - def test_capabilities_authorize_all(self): def auth(service, layers, environ, **kw): - eq_(environ['PATH_INFO'], '/tms/1.0.0') - eq_(service, 'tms') - eq_(len(layers), 6) - return {'authorized': 'full'} + assert environ["PATH_INFO"] == "/tms/1.0.0" + assert service == "tms" + assert len(layers) == 6 + return {"authorized": "full"} - resp = self.app.get(TMS_CAPABILITIES_REQ, extra_environ={'mapproxy.authorize': auth}) + resp = app.get(TMS_CAPABILITIES_REQ, extra_environ={"mapproxy.authorize": auth}) xml = resp.lxml - eq_(xml.xpath('//TileMap/@title'), ['layer 1a', 'layer 1b', 'layer 1', 'layer 2a', 'layer 2b1', 'layer 3']) + assert xml.xpath("//TileMap/@title") == [ + "layer 1a", + "layer 1b", + "layer 1", + "layer 2a", + "layer 2b1", + "layer 3", + ] + + def test_capabilities_authorize_none(self, app): - def test_capabilities_authorize_none(self): def auth(service, layers, **kw): - eq_(service, 'tms') - eq_(len(layers), 6) - return {'authorized': 'none'} - self.app.get(TMS_CAPABILITIES_REQ, extra_environ={'mapproxy.authorize': auth}, status=403) + assert service == "tms" + assert len(layers) == 6 + return {"authorized": "none"} + + app.get( + TMS_CAPABILITIES_REQ, extra_environ={"mapproxy.authorize": auth}, status=403 + ) + + def test_capabilities_unauthenticated(self, app): - def test_capabilities_unauthenticated(self): def auth(service, layers, **kw): - eq_(service, 'tms') - eq_(len(layers), 6) - return {'authorized': 'unauthenticated'} - self.app.get(TMS_CAPABILITIES_REQ, extra_environ={'mapproxy.authorize': auth}, status=401) + assert service == "tms" + assert len(layers) == 6 + return {"authorized": "unauthenticated"} + + app.get( + TMS_CAPABILITIES_REQ, extra_environ={"mapproxy.authorize": auth}, status=401 + ) + + def test_capabilities_authorize_partial(self, app): - def test_capabilities_authorize_partial(self): def auth(service, layers, **kw): - eq_(service, 'tms') - eq_(len(layers), 6) + assert service == "tms" + assert len(layers) == 6 return { - 'authorized': 'partial', - 'layers': { - 'layer1a': {'tile': True}, - 'layer1b': {'tile': False}, - 'layer2': {'tile': True}, - 'layer2b': {'tile': True}, - 'layer2b1': {'tile': True}, - } + "authorized": "partial", + "layers": { + "layer1a": {"tile": True}, + "layer1b": {"tile": False}, + "layer2": {"tile": True}, + "layer2b": {"tile": True}, + "layer2b1": {"tile": True}, + }, } - resp = self.app.get(TMS_CAPABILITIES_REQ, extra_environ={'mapproxy.authorize': auth}) + + resp = app.get(TMS_CAPABILITIES_REQ, extra_environ={"mapproxy.authorize": auth}) xml = resp.lxml - eq_(xml.xpath('//TileMap/@title'), ['layer 1a', 'layer 2b1']) + assert xml.xpath("//TileMap/@title") == ["layer 1a", "layer 2b1"] + + def test_layer_capabilities_authorize_none(self, app): - def test_layer_capabilities_authorize_none(self): def auth(service, layers, **kw): - eq_(service, 'tms') - eq_(len(layers), 1) - return { - 'authorized': 'none', - } - self.app.get(TMS_CAPABILITIES_REQ + '/layer1', extra_environ={'mapproxy.authorize': auth}, status=403) + assert service == "tms" + assert len(layers) == 1 + return {"authorized": "none"} + + app.get( + TMS_CAPABILITIES_REQ + "/layer1", + extra_environ={"mapproxy.authorize": auth}, + status=403, + ) + + def test_layer_capabilities_authorize_all(self, app): - def test_layer_capabilities_authorize_all(self): def auth(service, layers, **kw): - eq_(service, 'tms') - eq_(len(layers), 1) - return { - 'authorized': 'full', - } - resp = self.app.get(TMS_CAPABILITIES_REQ + '/layer1', extra_environ={'mapproxy.authorize': auth}) + assert service == "tms" + assert len(layers) == 1 + return {"authorized": "full"} + + resp = app.get( + TMS_CAPABILITIES_REQ + "/layer1", extra_environ={"mapproxy.authorize": auth} + ) xml = resp.lxml - eq_(xml.xpath('//TileMap/Title/text()'), ['layer 1']) + assert xml.xpath("//TileMap/Title/text()") == ["layer 1"] + + def test_layer_capabilities_authorize_partial(self, app): - def test_layer_capabilities_authorize_partial(self): def auth(service, layers, **kw): - eq_(service, 'tms') - eq_(len(layers), 1) - return { - 'authorized': 'partial', - 'layers': { - 'layer1': {'tile': True}, - } - } - resp = self.app.get(TMS_CAPABILITIES_REQ + '/layer1', extra_environ={'mapproxy.authorize': auth}) + assert service == "tms" + assert len(layers) == 1 + return {"authorized": "partial", "layers": {"layer1": {"tile": True}}} + + resp = app.get( + TMS_CAPABILITIES_REQ + "/layer1", extra_environ={"mapproxy.authorize": auth} + ) xml = resp.lxml - eq_(xml.xpath('//TileMap/Title/text()'), ['layer 1']) + assert xml.xpath("//TileMap/Title/text()") == ["layer 1"] + + def test_layer_capabilities_deny_partial(self, app): - def test_layer_capabilities_deny_partial(self): def auth(service, layers, **kw): - eq_(service, 'tms') - eq_(len(layers), 1) - return { - 'authorized': 'partial', - 'layers': { - 'layer1': {'tile': False}, - } - } - self.app.get(TMS_CAPABILITIES_REQ + '/layer1', extra_environ={'mapproxy.authorize': auth}, status=403) + assert service == "tms" + assert len(layers) == 1 + return {"authorized": "partial", "layers": {"layer1": {"tile": False}}} + + app.get( + TMS_CAPABILITIES_REQ + "/layer1", + extra_environ={"mapproxy.authorize": auth}, + status=403, + ) + + def test_get_tile(self, app): - def test_get_tile(self): def auth(service, layers, environ, query_extent, **kw): - eq_(environ['PATH_INFO'], '/tms/1.0.0/layer1_EPSG900913/0/0/0.png') - eq_(service, 'tms') - eq_(query_extent[0], 'EPSG:900913') - assert bbox_equals(query_extent[1], (-20037508.342789244, -20037508.342789244, 0, 0)) - eq_(len(layers), 1) - return { - 'authorized': 'partial', - 'layers': { - 'layer1': {'tile': True}, - } - } - resp = self.app.get(TMS_CAPABILITIES_REQ + '/layer1_EPSG900913/0/0/0.png', extra_environ={'mapproxy.authorize': auth}) - eq_(resp.content_type, 'image/png') + assert environ["PATH_INFO"] == "/tms/1.0.0/layer1_EPSG900913/0/0/0.png" + assert service == "tms" + assert query_extent[0] == "EPSG:900913" + assert bbox_equals( + query_extent[1], (-20037508.342789244, -20037508.342789244, 0, 0) + ) + assert len(layers) == 1 + return {"authorized": "partial", "layers": {"layer1": {"tile": True}}} + + resp = app.get( + TMS_CAPABILITIES_REQ + "/layer1_EPSG900913/0/0/0.png", + extra_environ={"mapproxy.authorize": auth}, + ) + assert resp.content_type == "image/png" assert resp.content_length > 1000 - def test_get_tile_global_limited_to(self): + def test_get_tile_global_limited_to(self, app): # check with limited_to for all layers auth_dict = { - 'authorized': 'partial', - 'limited_to': { - 'geometry': [-180, -89, -90, 89], - 'srs': 'EPSG:4326', - }, - 'layers': { - 'layer3': {'tile': True}, - } - } - self.check_get_tile_limited_to(auth_dict) + "authorized": "partial", + "limited_to": {"geometry": [-180, -89, -90, 89], "srs": "EPSG:4326"}, + "layers": {"layer3": {"tile": True}}, + } + self.check_get_tile_limited_to(app, auth_dict) - def test_get_tile_layer_limited_to(self): + def test_get_tile_layer_limited_to(self, app): # check with limited_to for one layer auth_dict = { - 'authorized': 'partial', - 'layers': { - 'layer3': { - 'tile': True, - 'limited_to': { - 'geometry': [-180, -89, -90, 89], - 'srs': 'EPSG:4326', - } - }, - } + "authorized": "partial", + "layers": { + "layer3": { + "tile": True, + "limited_to": { + "geometry": [-180, -89, -90, 89], + "srs": "EPSG:4326", + }, + } + }, } - self.check_get_tile_limited_to(auth_dict) + self.check_get_tile_limited_to(app, auth_dict) + + def check_get_tile_limited_to(self, app, auth_dict): - def check_get_tile_limited_to(self, auth_dict): def auth(service, layers, environ, query_extent, **kw): - eq_(environ['PATH_INFO'], '/tms/1.0.0/layer3/0/0/0.jpeg') - eq_(service, 'tms') - eq_(len(layers), 1) - eq_(query_extent[0], 'EPSG:900913') - assert bbox_equals(query_extent[1], (-20037508.342789244, -20037508.342789244, 0, 0)) + assert environ["PATH_INFO"] == "/tms/1.0.0/layer3/0/0/0.jpeg" + assert service == "tms" + assert len(layers) == 1 + assert query_extent[0] == "EPSG:900913" + assert bbox_equals( + query_extent[1], (-20037508.342789244, -20037508.342789244, 0, 0) + ) return auth_dict serv = MockServ(port=42423) - serv.expects('/1/0/0.png') - serv.returns(create_tmp_image((256, 256), color=(255, 0, 0)), headers={'content-type': 'image/png'}) + serv.expects("/1/0/0.png") + serv.returns( + create_tmp_image((256, 256), color=(255, 0, 0)), + headers={"content-type": "image/png"}, + ) with serv: - resp = self.app.get(TMS_CAPABILITIES_REQ + '/layer3/0/0/0.jpeg', extra_environ={'mapproxy.authorize': auth}) + resp = app.get( + TMS_CAPABILITIES_REQ + "/layer3/0/0/0.jpeg", + extra_environ={"mapproxy.authorize": auth}, + ) - eq_(resp.content_type, 'image/png') + assert resp.content_type == "image/png" img = img_from_buf(resp.body) - img = img.convert('RGBA') + img = img.convert("RGBA") # left part authorized, red - eq_(img.crop((0, 0, 127, 255)).getcolors()[0], (127*255, (255, 0, 0, 255))) + assert img.crop((0, 0, 127, 255)).getcolors()[0] == ( + 127 * 255, + (255, 0, 0, 255), + ) # right part not authorized, transparent - eq_(img.crop((129, 0, 255, 255)).getcolors()[0][1][3], 0) + assert img.crop((129, 0, 255, 255)).getcolors()[0][1][3] == 0 + + def test_get_tile_authorize_none(self, app): - def test_get_tile_authorize_none(self): def auth(service, layers, **kw): - eq_(service, 'tms') - eq_(len(layers), 1) - return { - 'authorized': 'none', - } - self.app.get(TMS_CAPABILITIES_REQ + '/layer1/0/0/0.png', extra_environ={'mapproxy.authorize': auth}, status=403) + assert service == "tms" + assert len(layers) == 1 + return {"authorized": "none"} + app.get( + TMS_CAPABILITIES_REQ + "/layer1/0/0/0.png", + extra_environ={"mapproxy.authorize": auth}, + status=403, + ) -class TestKMLAuth(SystemTest): - config = test_config - def test_superoverlay_authorize_all(self): - def auth(service, layers, environ, **kw): - eq_(environ['PATH_INFO'], '/kml/layer1/0/0/0.kml') - eq_(service, 'kml') - eq_(len(layers), 1) - return {'authorized': 'full'} +class TestKMLAuth(SysTest): + + def test_superoverlay_authorize_all(self, app): - resp = self.app.get('/kml/layer1/0/0/0.kml', extra_environ={'mapproxy.authorize': auth}) + def auth(service, layers, environ, **kw): + assert environ["PATH_INFO"] == "/kml/layer1/0/0/0.kml" + assert service == "kml" + assert len(layers) == 1 + return {"authorized": "full"} + + resp = app.get( + "/kml/layer1/0/0/0.kml", extra_environ={"mapproxy.authorize": auth} + ) xml = resp.lxml - eq_(xml.xpath('kml:Document/kml:name/text()', namespaces={'kml': 'http://www.opengis.net/kml/2.2'}), ['layer1']) + assert xml.xpath( + "kml:Document/kml:name/text()", + namespaces={"kml": "http://www.opengis.net/kml/2.2"}, + ) == ["layer1"] + + def test_superoverlay_authorize_none(self, app): - def test_superoverlay_authorize_none(self): def auth(service, layers, **kw): - eq_(service, 'kml') - eq_(len(layers), 1) - return {'authorized': 'none'} + assert service == "kml" + assert len(layers) == 1 + return {"authorized": "none"} + + app.get( + "/kml/layer1/0/0/0.kml", + extra_environ={"mapproxy.authorize": auth}, + status=403, + ) - self.app.get('/kml/layer1/0/0/0.kml', extra_environ={'mapproxy.authorize': auth}, status=403) + def test_superoverlay_unauthenticated(self, app): - def test_superoverlay_unauthenticated(self): def auth(service, layers, **kw): - eq_(service, 'kml') - eq_(len(layers), 1) - return {'authorized': 'unauthenticated'} + assert service == "kml" + assert len(layers) == 1 + return {"authorized": "unauthenticated"} - self.app.get('/kml/layer1/0/0/0.kml', extra_environ={'mapproxy.authorize': auth}, status=401) + app.get( + "/kml/layer1/0/0/0.kml", + extra_environ={"mapproxy.authorize": auth}, + status=401, + ) - def test_superoverlay_authorize_partial(self): - def auth(service, layers, query_extent, **kw): - eq_(service, 'kml') - eq_(len(layers), 1) - eq_(query_extent[0], 'EPSG:900913') - assert bbox_equals(query_extent[1], (-20037508.342789244, -20037508.342789244, 20037508.342789244, 20037508.342789244)) + def test_superoverlay_authorize_partial(self, app): - return { - 'authorized': 'partial', - 'layers': { - 'layer1': {'tile': True}, - } - } - resp = self.app.get('/kml/layer1/0/0/0.kml', extra_environ={'mapproxy.authorize': auth}) + def auth(service, layers, query_extent, **kw): + assert service == "kml" + assert len(layers) == 1 + assert query_extent[0] == "EPSG:900913" + assert bbox_equals( + query_extent[1], + ( + -20037508.342789244, + -20037508.342789244, + 20037508.342789244, + 20037508.342789244, + ), + ) + + return {"authorized": "partial", "layers": {"layer1": {"tile": True}}} + + resp = app.get( + "/kml/layer1/0/0/0.kml", extra_environ={"mapproxy.authorize": auth} + ) xml = resp.lxml - eq_(xml.xpath('kml:Document/kml:name/text()', namespaces={'kml': 'http://www.opengis.net/kml/2.2'}), ['layer1']) + assert xml.xpath( + "kml:Document/kml:name/text()", + namespaces={"kml": "http://www.opengis.net/kml/2.2"}, + ) == ["layer1"] + + def test_superoverlay_deny_partial(self, app): - def test_superoverlay_deny_partial(self): def auth(service, layers, **kw): - eq_(service, 'kml') - eq_(len(layers), 1) - return { - 'authorized': 'partial', - 'layers': { - 'layer1': {'tile': False}, - } - } - self.app.get('/kml/layer1/0/0/0.kml', extra_environ={'mapproxy.authorize': auth}, status=403) + assert service == "kml" + assert len(layers) == 1 + return {"authorized": "partial", "layers": {"layer1": {"tile": False}}} + + app.get( + "/kml/layer1/0/0/0.kml", + extra_environ={"mapproxy.authorize": auth}, + status=403, + ) - def test_get_tile_global_limited_to(self): + def test_get_tile_global_limited_to(self, app): # check with limited_to for all layers auth_dict = { - 'authorized': 'partial', - 'limited_to': { - 'geometry': [-180, -89, -90, 89], - 'srs': 'EPSG:4326', - }, - 'layers': { - 'layer3': {'tile': True}, - } - } - self.check_get_tile_limited_to(auth_dict) + "authorized": "partial", + "limited_to": {"geometry": [-180, -89, -90, 89], "srs": "EPSG:4326"}, + "layers": {"layer3": {"tile": True}}, + } + self.check_get_tile_limited_to(app, auth_dict) - def test_get_tile_layer_limited_to(self): + def test_get_tile_layer_limited_to(self, app): # check with limited_to for one layer auth_dict = { - 'authorized': 'partial', - 'layers': { - 'layer3': { - 'tile': True, - 'limited_to': { - 'geometry': [-180, -89, -90, 89], - 'srs': 'EPSG:4326', - } - }, - } + "authorized": "partial", + "layers": { + "layer3": { + "tile": True, + "limited_to": { + "geometry": [-180, -89, -90, 89], + "srs": "EPSG:4326", + }, + } + }, } - self.check_get_tile_limited_to(auth_dict) + self.check_get_tile_limited_to(app, auth_dict) + + def check_get_tile_limited_to(self, app, auth_dict): - def check_get_tile_limited_to(self, auth_dict): def auth(service, layers, environ, query_extent, **kw): - eq_(environ['PATH_INFO'], '/kml/layer3_EPSG900913/1/0/0.jpeg') - eq_(service, 'kml') - eq_(len(layers), 1) - eq_(query_extent[0], 'EPSG:900913') - assert bbox_equals(query_extent[1], (-20037508.342789244, -20037508.342789244, 0, 0)) + assert environ["PATH_INFO"] == "/kml/layer3_EPSG900913/1/0/0.jpeg" + assert service == "kml" + assert len(layers) == 1 + assert query_extent[0] == "EPSG:900913" + assert bbox_equals( + query_extent[1], (-20037508.342789244, -20037508.342789244, 0, 0) + ) return auth_dict serv = MockServ(port=42423) - serv.expects('/1/0/0.png') - serv.returns(create_tmp_image((256, 256), color=(255, 0, 0)), headers={'content-type': 'image/png'}) + serv.expects("/1/0/0.png") + serv.returns( + create_tmp_image((256, 256), color=(255, 0, 0)), + headers={"content-type": "image/png"}, + ) with serv: - resp = self.app.get('/kml/layer3_EPSG900913/1/0/0.jpeg', extra_environ={'mapproxy.authorize': auth}) + resp = app.get( + "/kml/layer3_EPSG900913/1/0/0.jpeg", + extra_environ={"mapproxy.authorize": auth}, + ) - eq_(resp.content_type, 'image/png') + assert resp.content_type == "image/png" img = img_from_buf(resp.body) - img = img.convert('RGBA') + img = img.convert("RGBA") # left part authorized, red - eq_(img.crop((0, 0, 127, 255)).getcolors()[0], (127*255, (255, 0, 0, 255))) + assert img.crop((0, 0, 127, 255)).getcolors()[0] == ( + 127 * 255, + (255, 0, 0, 255), + ) # right part not authorized, transparent - eq_(img.crop((129, 0, 255, 255)).getcolors()[0][1][3], 0) + assert img.crop((129, 0, 255, 255)).getcolors()[0][1][3] == 0 -WMTS_CAPABILITIES_REQ = '/wmts/1.0.0/WMTSCapabilities.xml' +WMTS_CAPABILITIES_REQ = "/wmts/1.0.0/WMTSCapabilities.xml" -class TestWMTSAuth(SystemTest): - config = test_config - def test_capabilities_authorize_all(self): - def auth(service, layers, environ, **kw): - eq_(environ['PATH_INFO'], '/wmts/1.0.0/WMTSCapabilities.xml') - eq_(service, 'wmts') - eq_(len(layers), 6) - return {'authorized': 'full'} +class TestWMTSAuth(SysTest): + + def test_capabilities_authorize_all(self, app): - resp = self.app.get(WMTS_CAPABILITIES_REQ, extra_environ={'mapproxy.authorize': auth}) + def auth(service, layers, environ, **kw): + assert environ["PATH_INFO"] == "/wmts/1.0.0/WMTSCapabilities.xml" + assert service == "wmts" + assert len(layers) == 6 + return {"authorized": "full"} + + resp = app.get( + WMTS_CAPABILITIES_REQ, extra_environ={"mapproxy.authorize": auth} + ) xml = resp.lxml - eq_(set(xml.xpath('//wmts:Layer/ows:Title/text()', - namespaces={'wmts': 'http://www.opengis.net/wmts/1.0', 'ows': 'http://www.opengis.net/ows/1.1'})), - set(['layer 1b', 'layer 1a', 'layer 2a', 'layer 2b1', 'layer 1', 'layer 3'])) + assert set( + xml.xpath( + "//wmts:Layer/ows:Title/text()", + namespaces={ + "wmts": "http://www.opengis.net/wmts/1.0", + "ows": "http://www.opengis.net/ows/1.1", + }, + ) + ) == set( + ["layer 1b", "layer 1a", "layer 2a", "layer 2b1", "layer 1", "layer 3"] + ) + + def test_capabilities_authorize_none(self, app): - def test_capabilities_authorize_none(self): def auth(service, layers, **kw): - eq_(service, 'wmts') - eq_(len(layers), 6) - return {'authorized': 'none'} - self.app.get(WMTS_CAPABILITIES_REQ, extra_environ={'mapproxy.authorize': auth}, status=403) + assert service == "wmts" + assert len(layers) == 6 + return {"authorized": "none"} + + app.get( + WMTS_CAPABILITIES_REQ, + extra_environ={"mapproxy.authorize": auth}, + status=403, + ) + + def test_capabilities_unauthenticated(self, app): - def test_capabilities_unauthenticated(self): def auth(service, layers, **kw): - eq_(service, 'wmts') - eq_(len(layers), 6) - return {'authorized': 'unauthenticated'} - self.app.get(WMTS_CAPABILITIES_REQ, extra_environ={'mapproxy.authorize': auth}, status=401) + assert service == "wmts" + assert len(layers) == 6 + return {"authorized": "unauthenticated"} + + app.get( + WMTS_CAPABILITIES_REQ, + extra_environ={"mapproxy.authorize": auth}, + status=401, + ) + + def test_capabilities_authorize_partial(self, app): - def test_capabilities_authorize_partial(self): def auth(service, layers, **kw): - eq_(service, 'wmts') - eq_(len(layers), 6) + assert service == "wmts" + assert len(layers) == 6 return { - 'authorized': 'partial', - 'layers': { - 'layer1a': {'tile': True}, - 'layer1b': {'tile': False}, - 'layer2': {'tile': True}, - 'layer2b': {'tile': True}, - 'layer2b1': {'tile': True}, - } + "authorized": "partial", + "layers": { + "layer1a": {"tile": True}, + "layer1b": {"tile": False}, + "layer2": {"tile": True}, + "layer2b": {"tile": True}, + "layer2b1": {"tile": True}, + }, } - resp = self.app.get(WMTS_CAPABILITIES_REQ, extra_environ={'mapproxy.authorize': auth}) + + resp = app.get( + WMTS_CAPABILITIES_REQ, extra_environ={"mapproxy.authorize": auth} + ) xml = resp.lxml - eq_(set(xml.xpath('//wmts:Layer/ows:Title/text()', - namespaces={'wmts': 'http://www.opengis.net/wmts/1.0', 'ows': 'http://www.opengis.net/ows/1.1'})), - set(['layer 1a', 'layer 2b1'])) + assert set( + xml.xpath( + "//wmts:Layer/ows:Title/text()", + namespaces={ + "wmts": "http://www.opengis.net/wmts/1.0", + "ows": "http://www.opengis.net/ows/1.1", + }, + ) + ) == set(["layer 1a", "layer 2b1"]) + + def test_get_tile(self, app): - def test_get_tile(self): def auth(service, layers, environ, query_extent, **kw): - eq_(environ['PATH_INFO'], '/wmts/layer1/GLOBAL_MERCATOR/0/0/0.png') - eq_(service, 'wmts') - eq_(len(layers), 1) - eq_(query_extent[0], 'EPSG:900913') - assert bbox_equals(query_extent[1], (-20037508.342789244, -20037508.342789244, 20037508.342789244, 20037508.342789244)) - return { - 'authorized': 'partial', - 'layers': { - 'layer1': {'tile': True}, - } - } - resp = self.app.get('/wmts/layer1/GLOBAL_MERCATOR/0/0/0.png', extra_environ={'mapproxy.authorize': auth}) - eq_(resp.content_type, 'image/png') + assert environ["PATH_INFO"] == "/wmts/layer1/GLOBAL_MERCATOR/0/0/0.png" + assert service == "wmts" + assert len(layers) == 1 + assert query_extent[0] == "EPSG:900913" + assert bbox_equals( + query_extent[1], + ( + -20037508.342789244, + -20037508.342789244, + 20037508.342789244, + 20037508.342789244, + ), + ) + return {"authorized": "partial", "layers": {"layer1": {"tile": True}}} + + resp = app.get( + "/wmts/layer1/GLOBAL_MERCATOR/0/0/0.png", + extra_environ={"mapproxy.authorize": auth}, + ) + assert resp.content_type == "image/png" assert resp.content_length > 1000 - def test_get_tile_global_limited_to(self): + def test_get_tile_global_limited_to(self, app): # check with limited_to for all layers auth_dict = { - 'authorized': 'partial', - 'limited_to': { - 'geometry': [-180, -89, -90, 89], - 'srs': 'EPSG:4326', - }, - 'layers': { - 'layer3': {'tile': True}, - } - } - self.check_get_tile_limited_to(auth_dict) + "authorized": "partial", + "limited_to": {"geometry": [-180, -89, -90, 89], "srs": "EPSG:4326"}, + "layers": {"layer3": {"tile": True}}, + } + self.check_get_tile_limited_to(app, auth_dict) - def test_get_tile_layer_limited_to(self): + def test_get_tile_layer_limited_to(self, app): # check with limited_to for one layer auth_dict = { - 'authorized': 'partial', - 'layers': { - 'layer3': { - 'tile': True, - 'limited_to': { - 'geometry': [-180, -89, -90, 89], - 'srs': 'EPSG:4326', - } - }, - } + "authorized": "partial", + "layers": { + "layer3": { + "tile": True, + "limited_to": { + "geometry": [-180, -89, -90, 89], + "srs": "EPSG:4326", + }, + } + }, } - self.check_get_tile_limited_to(auth_dict) + self.check_get_tile_limited_to(app, auth_dict) + + def check_get_tile_limited_to(self, app, auth_dict): - def check_get_tile_limited_to(self, auth_dict): def auth(service, layers, environ, query_extent, **kw): - eq_(environ['PATH_INFO'], '/wmts/layer3/GLOBAL_MERCATOR/1/0/0.jpeg') - eq_(service, 'wmts') - eq_(len(layers), 1) - eq_(query_extent[0], 'EPSG:900913') - assert bbox_equals(query_extent[1], (-20037508.342789244, 0, 0, 20037508.342789244)) + assert environ["PATH_INFO"] == "/wmts/layer3/GLOBAL_MERCATOR/1/0/0.jpeg" + assert service == "wmts" + assert len(layers) == 1 + assert query_extent[0] == "EPSG:900913" + assert bbox_equals( + query_extent[1], (-20037508.342789244, 0, 0, 20037508.342789244) + ) return auth_dict serv = MockServ(port=42423) - serv.expects('/1/0/1.png') - serv.returns(create_tmp_image((256, 256), color=(255, 0, 0)), headers={'content-type': 'image/png'}) + serv.expects("/1/0/1.png") + serv.returns( + create_tmp_image((256, 256), color=(255, 0, 0)), + headers={"content-type": "image/png"}, + ) with serv: - resp = self.app.get('/wmts/layer3/GLOBAL_MERCATOR/1/0/0.jpeg', extra_environ={'mapproxy.authorize': auth}) + resp = app.get( + "/wmts/layer3/GLOBAL_MERCATOR/1/0/0.jpeg", + extra_environ={"mapproxy.authorize": auth}, + ) - eq_(resp.content_type, 'image/png') + assert resp.content_type == "image/png" img = img_from_buf(resp.body) - img = img.convert('RGBA') + img = img.convert("RGBA") # left part authorized, red - eq_(img.crop((0, 0, 127, 255)).getcolors()[0], (127*255, (255, 0, 0, 255))) + assert img.crop((0, 0, 127, 255)).getcolors()[0] == ( + 127 * 255, + (255, 0, 0, 255), + ) # right part not authorized, transparent - eq_(img.crop((129, 0, 255, 255)).getcolors()[0][1][3], 0) + assert img.crop((129, 0, 255, 255)).getcolors()[0][1][3] == 0 + + def test_get_tile_limited_to_outside(self, app): - def test_get_tile_limited_to_outside(self): def auth(service, layers, environ, **kw): - eq_(environ['PATH_INFO'], '/wmts/layer3/GLOBAL_MERCATOR/2/0/0.jpeg') - eq_(service, 'wmts') - eq_(len(layers), 1) + assert environ["PATH_INFO"] == "/wmts/layer3/GLOBAL_MERCATOR/2/0/0.jpeg" + assert service == "wmts" + assert len(layers) == 1 return { - 'authorized': 'partial', - 'limited_to': { - 'geometry': [0, -89, 90, 89], - 'srs': 'EPSG:4326', - }, - 'layers': { - 'layer3': {'tile': True}, - } + "authorized": "partial", + "limited_to": {"geometry": [0, -89, 90, 89], "srs": "EPSG:4326"}, + "layers": {"layer3": {"tile": True}}, } - resp = self.app.get('/wmts/layer3/GLOBAL_MERCATOR/2/0/0.jpeg', extra_environ={'mapproxy.authorize': auth}) + resp = app.get( + "/wmts/layer3/GLOBAL_MERCATOR/2/0/0.jpeg", + extra_environ={"mapproxy.authorize": auth}, + ) - eq_(resp.content_type, 'image/png') + assert resp.content_type == "image/png" is_transparent(resp.body) - def test_get_tile_limited_to_inside(self): + def test_get_tile_limited_to_inside(self, app): + def auth(service, layers, environ, **kw): - eq_(environ['PATH_INFO'], '/wmts/layer3/GLOBAL_MERCATOR/1/0/0.jpeg') - eq_(service, 'wmts') - eq_(len(layers), 1) - return { - 'authorized': 'partial', - 'limited_to': { - 'geometry': [-180, -89, 180, 89], - 'srs': 'EPSG:4326', - }, - 'layers': { - 'layer3': {'tile': True}, - } + assert environ["PATH_INFO"] == "/wmts/layer3/GLOBAL_MERCATOR/1/0/0.jpeg" + assert service == "wmts" + assert len(layers) == 1 + return { + "authorized": "partial", + "limited_to": {"geometry": [-180, -89, 180, 89], "srs": "EPSG:4326"}, + "layers": {"layer3": {"tile": True}}, } serv = MockServ(port=42423) - serv.expects('/1/0/1.png') - serv.returns(create_tmp_image((256, 256), color=(255, 0, 0)), headers={'content-type': 'image/png'}) + serv.expects("/1/0/1.png") + serv.returns( + create_tmp_image((256, 256), color=(255, 0, 0)), + headers={"content-type": "image/png"}, + ) with serv: - resp = self.app.get('/wmts/layer3/GLOBAL_MERCATOR/1/0/0.jpeg', extra_environ={'mapproxy.authorize': auth}) + resp = app.get( + "/wmts/layer3/GLOBAL_MERCATOR/1/0/0.jpeg", + extra_environ={"mapproxy.authorize": auth}, + ) - eq_(resp.content_type, 'image/jpeg') + assert resp.content_type == "image/jpeg" img = img_from_buf(resp.body) - eq_(img.getcolors()[0], (256*256, (255, 0, 0))) + assert img.getcolors()[0] == (256 * 256, (255, 0, 0)) + + def test_get_tile_kvp(self, app): - def test_get_tile_kvp(self): def auth(service, layers, environ, **kw): - eq_(environ['PATH_INFO'], '/service') - eq_(service, 'wmts') - eq_(len(layers), 1) - return { - 'authorized': 'partial', - 'layers': { - 'layer1': {'tile': True}, - } - } - resp = self.app.get('/service?service=WMTS&version=1.0.0&layer=layer1&request=GetTile&' - 'style=&tilematrixset=GLOBAL_MERCATOR&tilematrix=00&tilerow=0&tilecol=0&format=image/png', extra_environ={'mapproxy.authorize': auth}) - eq_(resp.content_type, 'image/png') + assert environ["PATH_INFO"] == "/service" + assert service == "wmts" + assert len(layers) == 1 + return {"authorized": "partial", "layers": {"layer1": {"tile": True}}} + + resp = app.get( + "/service?service=WMTS&version=1.0.0&layer=layer1&request=GetTile&" + "style=&tilematrixset=GLOBAL_MERCATOR&tilematrix=00&tilerow=0&tilecol=0&format=image/png", + extra_environ={"mapproxy.authorize": auth}, + ) + assert resp.content_type == "image/png" + + def test_get_tile_authorize_none(self, app): + + def auth(service, layers, **kw): + assert service == "wmts" + assert len(layers) == 1 + return {"authorized": "none"} + + app.get( + "/wmts/layer1/GLOBAL_MERCATOR/0/0/0.png", + extra_environ={"mapproxy.authorize": auth}, + status=403, + ) - def test_get_tile_authorize_none(self): - def auth(service, layers, **kw): - eq_(service, 'wmts') - eq_(len(layers), 1) - return { - 'authorized': 'none', - } - self.app.get('/wmts/layer1/GLOBAL_MERCATOR/0/0/0.png', extra_environ={'mapproxy.authorize': auth}, status=403) + def test_get_tile_authorize_none_kvp(self, app): - def test_get_tile_authorize_none_kvp(self): def auth(service, layers, environ, **kw): - eq_(environ['PATH_INFO'], '/service') - eq_(service, 'wmts') - eq_(len(layers), 1) - return { - 'authorized': 'partial', - 'layers': { - 'layer1': {'tile': False}, - } - } - self.app.get('/service?service=WMTS&version=1.0.0&layer=layer1&request=GetTile&' - 'style=&tilematrixset=GLOBAL_MERCATOR&tilematrix=00&tilerow=0&tilecol=0&format=image/png', - extra_environ={'mapproxy.authorize': auth}, status=403) + assert environ["PATH_INFO"] == "/service" + assert service == "wmts" + assert len(layers) == 1 + return {"authorized": "partial", "layers": {"layer1": {"tile": False}}} + + app.get( + "/service?service=WMTS&version=1.0.0&layer=layer1&request=GetTile&" + "style=&tilematrixset=GLOBAL_MERCATOR&tilematrix=00&tilerow=0&tilecol=0&format=image/png", + extra_environ={"mapproxy.authorize": auth}, + status=403, + ) + + def test_get_featureinfo_kvp(self, app): + + def auth(service, layers, environ, **kw): + assert environ["PATH_INFO"] == "/service" + assert service == "wmts.featureinfo" + assert len(layers) == 1 + return {"authorized": "partial", "layers": {"layer1b": {"featureinfo": True}}} + -class TestDemoAuth(SystemTest): - config = test_config + serv = MockServ(port=42423) + serv.expects( + "/service?request=GetFeatureInfo&service=WMS&Version=1.1.1&SRS=EPSG:900913" + "&BBOX=-20037508.342789244,-20037508.342789244,20037508.342789244,20037508.342789244" + "&WIDTH=256&HEIGHT=256&styles=&FORMAT=image/png&X=10&Y=20" + "&query_layers=fi&layers=fi&info_format=application/json" + ) + serv.returns(b"{}") + with serv: + resp = app.get( + "/service?service=WMTS&version=1.0.0&layer=layer1b&request=GetFeatureInfo&" + "style=&tilematrixset=GLOBAL_MERCATOR&tilematrix=00&tilerow=0&tilecol=0&format=image/png" + "&infoformat=application/json&i=10&j=20", + extra_environ={"mapproxy.authorize": auth}, + ) + assert resp.content_type == "application/json" + + def test_get_featureinfo_kvp_authorized_none(self, app): - def test_authorize_all(self): def auth(service, layers, environ, **kw): - return {'authorized': 'full'} - self.app.get('/demo', extra_environ={'mapproxy.authorize': auth}) + assert environ["PATH_INFO"] == "/service" + assert service == "wmts.featureinfo" + assert len(layers) == 1 + return {"authorized": "partial", "layers": {"layer1b": {"tile": True}}} + + app.get( + "/service?service=WMTS&version=1.0.0&layer=layer1b&request=GetFeatureInfo&" + "style=&tilematrixset=GLOBAL_MERCATOR&tilematrix=00&tilerow=0&tilecol=0&format=image/png" + "&infoformat=application/json&i=10&j=20", + extra_environ={"mapproxy.authorize": auth}, + status=403, + ) + + def test_get_featureinfo_rest(self, app): - def test_authorize_none(self): def auth(service, layers, environ, **kw): - return {'authorized': 'none'} - self.app.get('/demo', extra_environ={'mapproxy.authorize': auth}, status=403) + assert environ["PATH_INFO"].startswith('/wmts/layer1b/') + assert service == "wmts.featureinfo" + assert len(layers) == 1 + return {"authorized": "partial", "layers": {"layer1b": {"featureinfo": True}}} + + + serv = MockServ(port=42423) + serv.expects( + "/service?request=GetFeatureInfo&service=WMS&Version=1.1.1&SRS=EPSG:900913" + "&BBOX=-20037508.342789244,-20037508.342789244,20037508.342789244,20037508.342789244" + "&WIDTH=256&HEIGHT=256&styles=&FORMAT=image/png&X=10&Y=20" + "&query_layers=fi&layers=fi&info_format=application/json" + ) + serv.returns(b"{}") + with serv: + resp = app.get( + "/wmts/layer1b/GLOBAL_MERCATOR/00/0/0/10/20.geojson", + extra_environ={"mapproxy.authorize": auth}, + ) + assert resp.content_type == "application/json" + + def test_get_featureinfo_rest_authorized_none(self, app): - def test_unauthenticated(self): def auth(service, layers, environ, **kw): - return {'authorized': 'unauthenticated'} - self.app.get('/demo', extra_environ={'mapproxy.authorize': auth}, status=401) + assert environ["PATH_INFO"].startswith('/wmts/layer1b/') + assert service == "wmts.featureinfo" + assert len(layers) == 1 + return {"authorized": "partial", "layers": {"layer1b": {"tile": True}}} + + app.get( + "/wmts/layer1b/GLOBAL_MERCATOR/00/0/0/10/20.geojson", + extra_environ={"mapproxy.authorize": auth}, + status=403, + ) - def test_superoverlay_authorize_none(self): - def auth(service, layers, **kw): - eq_(service, 'kml') - eq_(len(layers), 1) - return {'authorized': 'none'} +class TestDemoAuth(SysTest): + + def test_authorize_all(self, app): + + def auth(service, layers, environ, **kw): + return {"authorized": "full"} - self.app.get('/kml/layer1/0/0/0.kml', extra_environ={'mapproxy.authorize': auth}, status=403) + app.get("/demo", extra_environ={"mapproxy.authorize": auth}) + + def test_authorize_none(self, app): + + def auth(service, layers, environ, **kw): + return {"authorized": "none"} + + app.get("/demo", extra_environ={"mapproxy.authorize": auth}, status=403) + + def test_unauthenticated(self, app): + + def auth(service, layers, environ, **kw): + return {"authorized": "unauthenticated"} + + app.get("/demo", extra_environ={"mapproxy.authorize": auth}, status=401) + + def test_superoverlay_authorize_none(self, app): - def test_superoverlay_unauthenticated(self): def auth(service, layers, **kw): - eq_(service, 'kml') - eq_(len(layers), 1) - return {'authorized': 'unauthenticated'} + assert service == "kml" + assert len(layers) == 1 + return {"authorized": "none"} - self.app.get('/kml/layer1/0/0/0.kml', extra_environ={'mapproxy.authorize': auth}, status=401) + app.get( + "/kml/layer1/0/0/0.kml", + extra_environ={"mapproxy.authorize": auth}, + status=403, + ) + + def test_superoverlay_unauthenticated(self, app): + + def auth(service, layers, **kw): + assert service == "kml" + assert len(layers) == 1 + return {"authorized": "unauthenticated"} + app.get( + "/kml/layer1/0/0/0.kml", + extra_environ={"mapproxy.authorize": auth}, + status=401, + ) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_behind_proxy.py mapproxy-1.12.0/mapproxy/test/system/test_behind_proxy.py --- mapproxy-1.11.0/mapproxy/test/system/test_behind_proxy.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_behind_proxy.py 2019-08-30 07:34:08.000000000 +0000 @@ -15,61 +15,56 @@ from __future__ import division -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config +import pytest -test_config = {} -base_config = make_base_config(test_config) +from mapproxy.test.system import SysTest -def setup_module(): - module_setup(test_config, 'layer.yaml', with_cache_data=True) -def teardown_module(): - module_teardown(test_config) - - -class TestWMSBehindProxy(SystemTest): +class TestWMSBehindProxy(SysTest): """ Check WMS OnlineResources for requests behind HTTP proxies. """ - config = test_config + @pytest.fixture(scope='class') + def config_file(self): + return 'layer.yaml' - def test_no_proxy(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' + def test_no_proxy(self, app): + resp = app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' '&VERSION=1.1.0') assert '"http://localhost/service' in resp - def test_with_script_name(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' + def test_with_script_name(self, app): + resp = app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' '&VERSION=1.1.0', extra_environ={'HTTP_X_SCRIPT_NAME': '/foo'}) assert '"http://localhost/service' not in resp assert '"http://localhost/foo/service' in resp - def test_with_host(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' + def test_with_host(self, app): + resp = app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' '&VERSION=1.1.0', extra_environ={'HTTP_HOST': 'example.org'}) assert '"http://localhost/service' not in resp assert '"http://example.org/service' in resp - def test_with_host_and_script_name(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' + def test_with_host_and_script_name(self, app): + resp = app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' '&VERSION=1.1.0', extra_environ={'HTTP_X_SCRIPT_NAME': '/foo', 'HTTP_HOST': 'example.org'}) assert '"http://localhost/service' not in resp assert '"http://example.org/foo/service' in resp - def test_with_forwarded_host(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' + def test_with_forwarded_host(self, app): + resp = app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' '&VERSION=1.1.0', extra_environ={'HTTP_X_FORWARDED_HOST': 'example.org, bar.org'}) assert '"http://localhost/service' not in resp assert '"http://example.org/service' in resp - def test_with_forwarded_host_and_script_name(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' + def test_with_forwarded_host_and_script_name(self, app): + resp = app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' '&VERSION=1.1.0', extra_environ={'HTTP_X_FORWARDED_HOST': 'example.org', 'HTTP_X_SCRIPT_NAME': '/foo'}) assert '"http://localhost/service' not in resp assert '"http://example.org/foo/service' in resp - def test_with_forwarded_proto_and_script_name_and_host(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' + def test_with_forwarded_proto_and_script_name_and_host(self, app): + resp = app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' '&VERSION=1.1.0', extra_environ={ 'HTTP_X_FORWARDED_PROTO': 'https', 'HTTP_X_SCRIPT_NAME': '/foo', diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_bulk_meta_tiles.py mapproxy-1.12.0/mapproxy/test/system/test_bulk_meta_tiles.py --- mapproxy-1.11.0/mapproxy/test/system/test_bulk_meta_tiles.py 1970-01-01 00:00:00.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_bulk_meta_tiles.py 2019-08-30 07:34:08.000000000 +0000 @@ -0,0 +1,106 @@ +# This file is part of the MapProxy project. +# Copyright (C) 2016 Omniscale +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from mapproxy.request.wms import WMS111MapRequest +from mapproxy.test.image import tmp_image +from mapproxy.test.http import mock_httpd +from mapproxy.test.system import SysTest + + +@pytest.fixture(scope="module") +def config_file(): + return "cache_bulk_meta_tiles.yaml" + + +class TestCacheSource(SysTest): + + def test_tms_capabilities(self, app): + resp = app.get("/tms/1.0.0/") + xml = resp.lxml + + assert xml.xpath("count(//TileMap)") == 1 + + def test_get_map(self, app): + # request single tile via WMS and check that + map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + width="100", + height="100", + bbox="0,0,100000,100000", + layers="bulk", + srs="EPSG:3857", + format="image/png", + styles="", + request="GetMap", + ), + ) + + expected_reqs = [] + with tmp_image((256, 256), format="jpeg") as img: + img = img.read() + # bulk_cache has meta_size of [2, 2] + for tile in [(128, 128, 8), (128, 129, 8), (129, 128, 8), (129, 129, 8)]: + expected_reqs.append( + ( + { + "path": r"/tiles/%02d/000/000/%03d/000/000/%03d.png" + % (tile[2], tile[0], tile[1]) + }, + {"body": img, "headers": {"content-type": "image/png"}}, + ) + ) + with mock_httpd(("localhost", 42423), expected_reqs, unordered=True): + resp = app.get(map_req) + assert resp.content_type == "image/png" + + def test_get_tile(self, app, cache_dir): + expected_reqs = [] + with tmp_image((256, 256), format="jpeg") as img: + # bulk_cache has meta_size of [2, 2] + for tile in [(4, 3, 5), (5, 3, 5), (4, 2, 5), (5, 2, 5)]: + expected_reqs.append( + ( + { + "path": r"/tiles/%02d/000/000/%03d/000/000/%03d.png" + % (tile[2], tile[0], tile[1]) + }, + {"body": img.read(), "headers": {"content-type": "image/png"}}, + ) + ) + with mock_httpd(("localhost", 42423), expected_reqs, unordered=True): + resp = app.get("/tiles/1.0.0/bulk/EPSG900913/5/4/3.png") + assert resp.content_type == "image/png" + + assert cache_dir.join( + "bulk_cache_EPSG900913/05/000/000/004/000/000/002.png" + ).check() + assert cache_dir.join( + "bulk_cache_EPSG900913/05/000/000/004/000/000/003.png" + ).check() + assert cache_dir.join( + "bulk_cache_EPSG900913/05/000/000/005/000/000/002.png" + ).check() + assert cache_dir.join( + "bulk_cache_EPSG900913/05/000/000/005/000/000/003.png" + ).check() + + # access tile cached by previous bulk_meta_tile request + resp = app.get("/tiles/1.0.0/bulk/EPSG900913/5/5/3.png") + assert resp.content_type == "image/png" diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_cache_band_merge.py mapproxy-1.12.0/mapproxy/test/system/test_cache_band_merge.py --- mapproxy-1.11.0/mapproxy/test/system/test_cache_band_merge.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_cache_band_merge.py 2019-08-30 07:34:08.000000000 +0000 @@ -13,83 +13,91 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pytest + from mapproxy.request.wms import WMS111MapRequest from mapproxy.request.wmts import WMTS100CapabilitiesRequest from mapproxy.test.image import img_from_buf -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config -from nose.tools import eq_ - -test_config = {} -base_config = make_base_config(test_config) +from mapproxy.test.system import SysTest -def setup_module(): - module_setup(test_config, 'cache_band_merge.yaml', with_cache_data=True) -def teardown_module(): - module_teardown(test_config) +@pytest.fixture(scope="module") +def config_file(): + return "cache_band_merge.yaml" -class TestCacheSource(SystemTest): +@pytest.mark.usefixtures("fixture_cache_data") +class TestCacheSource(SysTest): # test various band merge configurations with # cached base tile 0/0/0.png (R: 50 G: 100 B: 200) - config = test_config - def setup(self): - SystemTest.setup(self) - self.common_cap_req = WMTS100CapabilitiesRequest(url='/service?', param=dict(service='WMTS', - version='1.0.0', request='GetCapabilities')) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='-180,0,0,80', width='100', height='100', - layers='dop_l', srs='EPSG:4326', format='image/png', - styles='', request='GetMap')) + self.common_cap_req = WMTS100CapabilitiesRequest( + url="/service?", + param=dict(service="WMTS", version="1.0.0", request="GetCapabilities"), + ) + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="-180,0,0,80", + width="100", + height="100", + layers="dop_l", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + ), + ) - def test_capabilities(self): + def test_capabilities(self, app): req = str(self.common_cap_req) - resp = self.app.get(req) - eq_(resp.content_type, 'application/xml') + resp = app.get(req) + assert resp.content_type == "application/xml" - def test_get_tile_021(self): - resp = self.app.get('/wmts/dop_021/GLOBAL_WEBMERCATOR/0/0/0.png') - eq_(resp.content_type, 'image/png') + def test_get_tile_021(self, app): + resp = app.get("/wmts/dop_021/GLOBAL_WEBMERCATOR/0/0/0.png") + assert resp.content_type == "image/png" img = img_from_buf(resp.body) - eq_(img.mode, 'RGB') - eq_(img.getpixel((0, 0)), (50, 200, 100)) + assert img.mode == "RGB" + assert img.getpixel((0, 0)) == (50, 200, 100) - def test_get_tile_l(self): - resp = self.app.get('/wmts/dop_l/GLOBAL_WEBMERCATOR/0/0/0.png') - eq_(resp.content_type, 'image/png') + def test_get_tile_l(self, app): + resp = app.get("/wmts/dop_l/GLOBAL_WEBMERCATOR/0/0/0.png") + assert resp.content_type == "image/png" img = img_from_buf(resp.body) - eq_(img.mode, 'L') - eq_(img.getpixel((0, 0)), int(50*0.25+0.7*100+0.05*200)) + assert img.mode == "L" + assert img.getpixel((0, 0)) == int(50 * 0.25 + 0.7 * 100 + 0.05 * 200) - def test_get_tile_0(self): - resp = self.app.get('/wmts/dop_0/GLOBAL_WEBMERCATOR/0/0/0.png') - eq_(resp.content_type, 'image/png') + def test_get_tile_0(self, app): + resp = app.get("/wmts/dop_0/GLOBAL_WEBMERCATOR/0/0/0.png") + assert resp.content_type == "image/png" img = img_from_buf(resp.body) - eq_(img.mode, 'RGB') # forced with image.mode - eq_(img.getpixel((0, 0)), (50, 50, 50)) + assert img.mode == "RGB" # forced with image.mode + assert img.getpixel((0, 0)) == (50, 50, 50) - def test_get_tile_0122(self): - resp = self.app.get('/wmts/dop_0122/GLOBAL_WEBMERCATOR/0/0/0.png') - eq_(resp.content_type, 'image/png') + def test_get_tile_0122(self, app): + resp = app.get("/wmts/dop_0122/GLOBAL_WEBMERCATOR/0/0/0.png") + assert resp.content_type == "image/png" img = img_from_buf(resp.body) - eq_(img.mode, 'RGBA') - eq_(img.getpixel((0, 0)), (50, 100, 200, 50)) + assert img.mode == "RGBA" + assert img.getpixel((0, 0)) == (50, 100, 200, 50) - def test_get_map_l(self): - resp = self.app.get(str(self.common_map_req)) - eq_(resp.content_type, 'image/png') + def test_get_map_l(self, app): + resp = app.get(str(self.common_map_req)) + assert resp.content_type == "image/png" img = img_from_buf(resp.body) - eq_(img.mode, 'L') - eq_(img.getpixel((0, 0)), int(50*0.25+0.7*100+0.05*200)) + assert img.mode == "L" + assert img.getpixel((0, 0)) == int(50 * 0.25 + 0.7 * 100 + 0.05 * 200) - def test_get_map_l_jpeg(self): - self.common_map_req.params.format = 'image/jpeg' - resp = self.app.get(str(self.common_map_req)) - eq_(resp.content_type, 'image/jpeg') + def test_get_map_l_jpeg(self, app): + self.common_map_req.params.format = "image/jpeg" + resp = app.get(str(self.common_map_req)) + assert resp.content_type == "image/jpeg" img = img_from_buf(resp.body) - eq_(img.mode, 'RGB') + assert img.mode == "RGB" # L converted to RGB for jpeg - eq_(img.getpixel((0, 0)), (92, 92, 92)) + assert img.getpixel((0, 0)) == (92, 92, 92) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_cache_geopackage.py mapproxy-1.12.0/mapproxy/test/system/test_cache_geopackage.py --- mapproxy-1.11.0/mapproxy/test/system/test_cache_geopackage.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_cache_geopackage.py 2019-08-30 07:34:08.000000000 +0000 @@ -17,104 +17,119 @@ import os import shutil +import sqlite3 from io import BytesIO +import pytest + from mapproxy.request.wms import WMS111MapRequest from mapproxy.test.http import MockServ from mapproxy.test.image import is_png, create_tmp_image -from mapproxy.test.system import prepare_env, create_app, module_teardown, SystemTest +from mapproxy.test.system import SysTest from mapproxy.cache.geopackage import GeopackageCache from mapproxy.grid import TileGrid -from nose.tools import eq_ -import sqlite3 - -test_config = {} - -def setup_module(): - prepare_env(test_config, 'cache_geopackage.yaml') - shutil.copy(os.path.join(test_config['fixture_dir'], 'cache.gpkg'), - test_config['base_dir']) - create_app(test_config) +@pytest.fixture(scope="module") +def config_file(): + return "cache_geopackage.yaml" -def teardown_module(): - module_teardown(test_config) +@pytest.fixture(scope="class") +def fixture_gpkg(base_dir): + shutil.copy( + os.path.join(os.path.dirname(__file__), "fixture", "cache.gpkg"), + base_dir.strpath, + ) -class TestGeopackageCache(SystemTest): - config = test_config - table_name = 'cache' +@pytest.mark.usefixtures("fixture_gpkg") +class TestGeopackageCache(SysTest): def setup(self): - SystemTest.setup(self) - self.common_map_req = WMS111MapRequest(url='/service?', - param=dict(service='WMS', - version='1.1.1', bbox='-180,-80,0,0', - width='200', height='200', - layers='gpkg', srs='EPSG:4326', - format='image/png', - styles='', request='GetMap')) - - def test_get_map_cached(self): - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="-180,-80,0,0", + width="200", + height="200", + layers="gpkg", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + ), + ) + + def test_get_map_cached(self, app): + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - def test_get_map_uncached(self): - assert os.path.exists(os.path.join(test_config['base_dir'], 'cache.gpkg')) # already created on startup + def test_get_map_uncached(self, app, base_dir): + assert base_dir.join("cache.gpkg").check() + # already created on startup - self.common_map_req.params.bbox = '-180,0,0,80' + self.common_map_req.params.bbox = "-180,0,0,80" serv = MockServ(port=42423) - serv.expects('/tiles/01/000/000/000/000/000/001.png') + serv.expects("/tiles/01/000/000/000/000/000/001.png") serv.returns(create_tmp_image((256, 256))) with serv: - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) # now cached - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - def test_bad_config_geopackage_no_gpkg_contents(self): - gpkg_file = os.path.join(test_config['base_dir'], 'cache.gpkg') - table_name = 'no_gpkg_contents' + def test_bad_config_geopackage_no_gpkg_contents(self, app, base_dir): + gpkg_file = base_dir.join("cache.gpkg").strpath + table_name = "no_gpkg_contents" with sqlite3.connect(gpkg_file) as db: - cur = db.execute('''SELECT name FROM sqlite_master WHERE type='table' AND name=?''', - (table_name,)) + cur = db.execute( + """SELECT name FROM sqlite_master WHERE type='table' AND name=?""", + (table_name,), + ) content = cur.fetchone() assert content[0] == table_name with sqlite3.connect(gpkg_file) as db: - cur = db.execute('''SELECT table_name FROM gpkg_contents WHERE table_name=?''', - (table_name,)) + cur = db.execute( + """SELECT table_name FROM gpkg_contents WHERE table_name=?""", + (table_name,), + ) content = cur.fetchone() assert not content GeopackageCache(gpkg_file, TileGrid(srs=4326), table_name=table_name) with sqlite3.connect(gpkg_file) as db: - cur = db.execute('''SELECT table_name FROM gpkg_contents WHERE table_name=?''', - (table_name,)) + cur = db.execute( + """SELECT table_name FROM gpkg_contents WHERE table_name=?""", + (table_name,), + ) content = cur.fetchone() assert content[0] == table_name - def test_bad_config_geopackage_no_spatial_ref_sys(self): - gpkg_file = os.path.join(test_config['base_dir'], 'cache.gpkg') + def test_bad_config_geopackage_no_spatial_ref_sys(self, base_dir): + gpkg_file = base_dir.join("cache.gpkg").strpath organization_coordsys_id = 3785 - table_name='no_gpkg_spatial_ref_sys' + table_name = "no_gpkg_spatial_ref_sys" with sqlite3.connect(gpkg_file) as db: - cur = db.execute('''SELECT organization_coordsys_id FROM gpkg_spatial_ref_sys WHERE organization_coordsys_id=?''', - (organization_coordsys_id,)) + cur = db.execute( + """SELECT organization_coordsys_id FROM gpkg_spatial_ref_sys WHERE organization_coordsys_id=?""", + (organization_coordsys_id,), + ) content = cur.fetchone() assert not content @@ -122,7 +137,8 @@ with sqlite3.connect(gpkg_file) as db: cur = db.execute( - '''SELECT organization_coordsys_id FROM gpkg_spatial_ref_sys WHERE organization_coordsys_id=?''', - (organization_coordsys_id,)) + """SELECT organization_coordsys_id FROM gpkg_spatial_ref_sys WHERE organization_coordsys_id=?""", + (organization_coordsys_id,), + ) content = cur.fetchone() assert content[0] == organization_coordsys_id diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_cache_grid_names.py mapproxy-1.12.0/mapproxy/test/system/test_cache_grid_names.py --- mapproxy-1.11.0/mapproxy/test/system/test_cache_grid_names.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_cache_grid_names.py 2019-08-30 07:34:08.000000000 +0000 @@ -13,81 +13,77 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os +import pytest + from mapproxy.test.image import tmp_image from mapproxy.test.http import mock_httpd -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config -from nose.tools import eq_ +from mapproxy.test.system import SysTest + + +@pytest.fixture(scope="module") +def config_file(): + return "cache_grid_names.yaml" -test_config = {} -base_config = make_base_config(test_config) -def setup_module(): - module_setup(test_config, 'cache_grid_names.yaml') +class TestCacheGridNames(SysTest): -def teardown_module(): - module_teardown(test_config) - -class TestCacheGridNames(SystemTest): - config = test_config - - def test_tms_capabilities(self): - resp = self.app.get('/tms/1.0.0/') - assert 'Cached Layer' in resp - assert 'wms_cache/utm32n' in resp - assert 'wms_cache_utm32n' not in resp + def test_tms_capabilities(self, app): + resp = app.get("/tms/1.0.0/") + assert "Cached Layer" in resp + assert "wms_cache/utm32n" in resp + assert "wms_cache_utm32n" not in resp xml = resp.lxml - assert xml.xpath('count(//TileMap)') == 2 + assert xml.xpath("count(//TileMap)") == 2 - def test_tms_layer_capabilities(self): - resp = self.app.get('/tms/1.0.0/wms_cache/utm32n') - assert 'Cached Layer' in resp - assert 'wms_cache/utm32n' in resp - assert 'wms_cache_utm32n' not in resp + def test_tms_layer_capabilities(self, app): + resp = app.get("/tms/1.0.0/wms_cache/utm32n") + assert "Cached Layer" in resp + assert "wms_cache/utm32n" in resp + assert "wms_cache_utm32n" not in resp xml = resp.lxml - eq_(xml.xpath('count(//TileSet)'), 12) + assert xml.xpath("count(//TileSet)") == 12 - def test_kml(self): - resp = self.app.get('/kml/wms_cache/utm32n/4/2/2.kml') - assert b'wms_cache/utm32n' in resp.body - - def test_get_tile(self): - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/service?LAYERs=bar&SERVICE=WMS&FORMAT=image%2Fjpeg' - '&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A25832&styles=' - '&VERSION=1.1.1&BBOX=283803.311362,5609091.90862,319018.942566,5644307.53982' - '&WIDTH=256'}, - {'body': img.read(), 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - resp = self.app.get('/tms/1.0.0/wms_cache/utm32n/4/2/2.jpeg') - eq_(resp.content_type, 'image/jpeg') - self.created_tiles.append('wms_cache/utm32n/04/000/000/002/000/000/002.jpeg') + def test_kml(self, app): + resp = app.get("/kml/wms_cache/utm32n/4/2/2.kml") + assert b"wms_cache/utm32n" in resp.body + + def test_get_tile(self, app, cache_dir): + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + { + "path": r"/service?LAYERs=bar&SERVICE=WMS&FORMAT=image%2Fjpeg" + "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A25832&styles=" + "&VERSION=1.1.1&&WIDTH=256" + "&BBOX=283803.311362,5609091.90862,319018.942566,5644307.53982" + }, + {"body": img.read(), "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + resp = app.get("/tms/1.0.0/wms_cache/utm32n/4/2/2.jpeg") + assert resp.content_type == "image/jpeg" + assert cache_dir.join( + "wms_cache/utm32n/04/000/000/002/000/000/002.jpeg" + ).check() - def test_get_tile_no_grid_name(self): + def test_get_tile_no_grid_name(self, app, cache_dir): # access tiles with grid name from TMS but cache still uses old SRS-code path - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/service?LAYERs=bar&SERVICE=WMS&FORMAT=image%2Fjpeg' - '&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A25832&styles=' - '&VERSION=1.1.1&BBOX=283803.311362,5609091.90862,319018.942566,5644307.53982' - '&WIDTH=256'}, - {'body': img.read(), 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - resp = self.app.get('/tms/1.0.0/wms_cache_no_grid_name/utm32n/4/2/2.jpeg') - eq_(resp.content_type, 'image/jpeg') - self.created_tiles.append('wms_cache_no_grid_name_EPSG25832/04/000/000/002/000/000/002.jpeg') - - def created_tiles_filenames(self): - base_dir = base_config().cache.base_dir - for filename in self.created_tiles: - yield os.path.join(base_dir, filename) - - def check_created_tiles(self): - for filename in self.created_tiles_filenames(): - if not os.path.exists(filename): - assert False, "didn't found tile " + filename - - def teardown(self): - self.check_created_tiles() - for filename in self.created_tiles_filenames(): - if os.path.exists(filename): - os.remove(filename) + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + { + "path": r"/service?LAYERs=bar&SERVICE=WMS&FORMAT=image%2Fjpeg" + "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A25832&styles=" + "&VERSION=1.1.1&WIDTH=256" + "&BBOX=283803.311362,5609091.90862,319018.942566,5644307.53982" + }, + {"body": img.read(), "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + resp = app.get("/tms/1.0.0/wms_cache_no_grid_name/utm32n/4/2/2.jpeg") + assert resp.content_type == "image/jpeg" + assert cache_dir.join( + "wms_cache_no_grid_name_EPSG25832/04/000/000/002/000/000/002.jpeg" + ).check() diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_cache_mbtiles.py mapproxy-1.12.0/mapproxy/test/system/test_cache_mbtiles.py --- mapproxy-1.11.0/mapproxy/test/system/test_cache_mbtiles.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_cache_mbtiles.py 2019-08-30 07:34:08.000000000 +0000 @@ -23,53 +23,63 @@ from mapproxy.request.wms import WMS111MapRequest from mapproxy.test.http import MockServ from mapproxy.test.image import is_png, create_tmp_image -from mapproxy.test.system import prepare_env, create_app, module_teardown, SystemTest -from nose.tools import eq_ +from mapproxy.test.system import SysTest -test_config = {} +import pytest -def setup_module(): - prepare_env(test_config, 'cache_mbtiles.yaml') - shutil.copy(os.path.join(test_config['fixture_dir'], 'cache.mbtiles'), - test_config['base_dir']) - create_app(test_config) +@pytest.fixture(scope="module") +def config_file(): + return "cache_mbtiles.yaml" -def teardown_module(): - module_teardown(test_config) -class TestMBTilesCache(SystemTest): - config = test_config +@pytest.fixture(scope="class") +def fixture_gpkg(base_dir): + shutil.copy( + os.path.join(os.path.dirname(__file__), "fixture", "cache.mbtiles"), + base_dir.strpath, + ) + + +@pytest.mark.usefixtures("fixture_gpkg") +class TestMBTilesCache(SysTest): + def setup(self): - SystemTest.setup(self) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='-180,-80,0,0', width='200', height='200', - layers='mb', srs='EPSG:4326', format='image/png', - styles='', request='GetMap')) - - def test_get_map_cached(self): - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="-180,-80,0,0", + width="200", + height="200", + layers="mb", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + ), + ) + + def test_get_map_cached(self, app): + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - def test_get_map_uncached(self): - mbtiles_file = os.path.join(test_config['base_dir'], 'cache.mbtiles') - - assert os.path.exists(mbtiles_file) # already created on startup - - self.common_map_req.params.bbox = '-180,0,0,80' + def test_get_map_uncached(self, app): + self.common_map_req.params.bbox = "-180,0,0,80" serv = MockServ(port=42423) - serv.expects('/tiles/01/000/000/000/000/000/001.png') + serv.expects("/tiles/01/000/000/000/000/000/001.png") serv.returns(create_tmp_image((256, 256))) with serv: - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) # now cached - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_cache_s3.py mapproxy-1.12.0/mapproxy/test/system/test_cache_s3.py --- mapproxy-1.11.0/mapproxy/test/system/test_cache_s3.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_cache_s3.py 2019-08-30 07:34:08.000000000 +0000 @@ -15,14 +15,16 @@ from __future__ import division +import sys + from io import BytesIO from mapproxy.request.wms import WMS111MapRequest from mapproxy.test.image import is_png, create_tmp_image -from mapproxy.test.system import prepare_env, create_app, module_teardown, SystemTest +from mapproxy.test.system import SysTest + +import pytest -from nose.tools import eq_ -from nose.plugins.skip import SkipTest try: import boto3 @@ -32,84 +34,82 @@ mock_s3 = None -test_config = {} - -_mock = None - -def setup_module(): - if not mock_s3 or not boto3: - raise SkipTest("boto3 and moto required for S3 tests") - - global _mock - _mock = mock_s3() - _mock.start() - - boto3.client("s3").create_bucket(Bucket="default_bucket") - boto3.client("s3").create_bucket(Bucket="tiles") - boto3.client("s3").create_bucket(Bucket="reversetiles") - - prepare_env(test_config, 'cache_s3.yaml') - create_app(test_config) - -def teardown_module(): - module_teardown(test_config) - _mock.stop() - -class TestS3Cache(SystemTest): - config = test_config - table_name = 'cache' +@pytest.fixture(scope="module") +def config_file(): + return "cache_s3.yaml" + + +@pytest.fixture(scope="module") +def s3_buckets(): + with mock_s3(): + boto3.client("s3").create_bucket(Bucket="default_bucket") + boto3.client("s3").create_bucket(Bucket="tiles") + boto3.client("s3").create_bucket(Bucket="reversetiles") + + yield + + +@pytest.mark.skipif(not (boto3 and mock_s3), reason="boto3 and moto required") +@pytest.mark.xfail( + sys.version_info[:2] in ((3, 4), (3, 5)), + reason="moto tests unreliable with Python 3.4/3.5", +) +@pytest.mark.usefixtures("s3_buckets") +class TestS3Cache(SysTest): def setup(self): - SystemTest.setup(self) - self.common_map_req = WMS111MapRequest(url='/service?', - param=dict(service='WMS', - version='1.1.1', bbox='-150,-40,-140,-30', - width='100', height='100', - layers='default', srs='EPSG:4326', - format='image/png', - styles='', request='GetMap')) + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="-150,-40,-140,-30", + width="100", + height="100", + layers="default", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + ), + ) - def test_get_map_cached(self): + def test_get_map_cached(self, app): # mock_s3 interferes with MockServ, use boto to manually upload tile tile = create_tmp_image((256, 256)) boto3.client("s3").upload_fileobj( - BytesIO(tile), - Bucket='default_bucket', - Key='default_cache/WebMerc/4/1/9.png', + BytesIO(tile), + Bucket="default_bucket", + Key="default_cache/WebMerc/4/1/9.png", ) - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - - def test_get_map_cached_quadkey(self): + def test_get_map_cached_quadkey(self, app): # mock_s3 interferes with MockServ, use boto to manually upload tile tile = create_tmp_image((256, 256)) boto3.client("s3").upload_fileobj( - BytesIO(tile), - Bucket='tiles', - Key='quadkeytiles/2003.png', + BytesIO(tile), Bucket="tiles", Key="quadkeytiles/2003.png" ) - self.common_map_req.params.layers = 'quadkey' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + self.common_map_req.params.layers = "quadkey" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - def test_get_map_cached_reverse_tms(self): + def test_get_map_cached_reverse_tms(self, app): # mock_s3 interferes with MockServ, use boto to manually upload tile tile = create_tmp_image((256, 256)) boto3.client("s3").upload_fileobj( - BytesIO(tile), - Bucket='tiles', - Key='reversetiles/9/1/4.png', + BytesIO(tile), Bucket="tiles", Key="reversetiles/9/1/4.png" ) - self.common_map_req.params.layers = 'reverse' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + self.common_map_req.params.layers = "reverse" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_cache_source.py mapproxy-1.12.0/mapproxy/test/system/test_cache_source.py --- mapproxy-1.11.0/mapproxy/test/system/test_cache_source.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_cache_source.py 2019-08-30 07:34:08.000000000 +0000 @@ -13,99 +13,134 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os +import pytest + from mapproxy.request.wms import WMS111MapRequest from mapproxy.test.image import tmp_image from mapproxy.test.http import mock_httpd -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config -from nose.tools import eq_ +from mapproxy.test.system import SysTest -test_config = {} -base_config = make_base_config(test_config) -def setup_module(): - module_setup(test_config, 'cache_source.yaml') +@pytest.fixture(scope="module") +def config_file(): + return "cache_source.yaml" -def teardown_module(): - module_teardown(test_config) -class TestCacheSource(SystemTest): - config = test_config +class TestCacheSource(SysTest): - def test_tms_capabilities(self): - resp = self.app.get('/tms/1.0.0/') - assert 'transformed tile source' in resp + def test_tms_capabilities(self, app): + resp = app.get("/tms/1.0.0/") + assert "transformed tile source" in resp xml = resp.lxml - assert xml.xpath('count(//TileMap)') == 3 + assert xml.xpath("count(//TileMap)") == 3 - def test_get_map_through_cache(self): - map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', width='100', height='100', - bbox='432890.564641,5872387.45834,466833.867667,5928359.08814', - layers='tms_transf', srs='EPSG:25832', format='image/png', - styles='', request='GetMap')) + def test_get_map_through_cache(self, app): + map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + width="100", + height="100", + bbox="432890.564641,5872387.45834,466833.867667,5928359.08814", + layers="tms_transf", + srs="EPSG:25832", + format="image/png", + styles="", + request="GetMap", + ), + ) expected_reqs = [] - with tmp_image((256, 256), format='jpeg') as img: + with tmp_image((256, 256), format="jpeg") as img: img = img.read() # tms_cache_out has meta_size of [2, 2] but we need larger extent for transformation - for tile in [(132, 172, 8), (133, 172, 8), (134, 172, 8), (132, 173, 8), - (133, 173, 8), (134, 173, 8), (132, 174, 8), (133, 174, 8), (134, 174, 8)]: + for tile in [ + (132, 172, 8), + (133, 172, 8), + (134, 172, 8), + (132, 173, 8), + (133, 173, 8), + (134, 173, 8), + (132, 174, 8), + (133, 174, 8), + (134, 174, 8), + ]: expected_reqs.append( - ({'path': r'/tiles/%02d/000/000/%03d/000/000/%03d.png' % (tile[2], tile[0], tile[1])}, - {'body': img, 'headers': {'content-type': 'image/png'}})) - with mock_httpd(('localhost', 42423), expected_reqs, unordered=True): - resp = self.app.get(map_req) - eq_(resp.content_type, 'image/png') + ( + { + "path": r"/tiles/%02d/000/000/%03d/000/000/%03d.png" + % (tile[2], tile[0], tile[1]) + }, + {"body": img, "headers": {"content-type": "image/png"}}, + ) + ) + with mock_httpd(("localhost", 42423), expected_reqs, unordered=True): + resp = app.get(map_req) + assert resp.content_type == "image/png" - def test_get_tile_through_cache(self): + def test_get_tile_through_cache(self, app, cache_dir): # request tile from tms_transf, # should get tile from tms_source via tms_cache_in/out expected_reqs = [] - with tmp_image((256, 256), format='jpeg') as img: + with tmp_image((256, 256), format="jpeg") as img: for tile in [(8, 11, 4), (8, 10, 4)]: expected_reqs.append( - ({'path': r'/tiles/%02d/000/000/%03d/000/000/%03d.png' % (tile[2], tile[0], tile[1])}, - {'body': img.read(), 'headers': {'content-type': 'image/png'}})) - with mock_httpd(('localhost', 42423), expected_reqs, unordered=True): - resp = self.app.get('/tms/1.0.0/tms_transf/EPSG25832/0/0/0.png') - eq_(resp.content_type, 'image/png') - - self.created_tiles.append('tms_cache_out_EPSG25832/00/000/000/000/000/000/000.png') + ( + { + "path": r"/tiles/%02d/000/000/%03d/000/000/%03d.png" + % (tile[2], tile[0], tile[1]) + }, + {"body": img.read(), "headers": {"content-type": "image/png"}}, + ) + ) + with mock_httpd(("localhost", 42423), expected_reqs, unordered=True): + resp = app.get("/tms/1.0.0/tms_transf/EPSG25832/0/0/0.png") + assert resp.content_type == "image/png" + + assert cache_dir.join( + "tms_cache_out_EPSG25832/00/000/000/000/000/000/000.png" + ).check() - def test_get_tile_from_sub_grid(self): + def test_get_tile_from_sub_grid(self, app, cache_dir): # create tile in old cache - tile_filename = os.path.join(self.config['cache_dir'], 'old_cache_EPSG3857/01/000/000/001/000/000/000.png') - os.makedirs(os.path.dirname(tile_filename)) + tile_filename = cache_dir.join( + "old_cache_EPSG3857/01/000/000/001/000/000/000.png" + ) # use text to check that mapproxy does not access the tile as image - open(tile_filename, 'wb').write(b'foo') + tile_filename.write_binary(b"foo", ensure=True) # access new cache, should get existing tile from old cache - resp = self.app.get('/tiles/new_cache_EPSG3857/0/0/0.png') - eq_(resp.content_type, 'image/png') - eq_(resp.body, b'foo') + resp = app.get("/tiles/new_cache_EPSG3857/0/0/0.png") + assert resp.content_type == "image/png" + assert resp.body == b"foo" + + assert cache_dir.join( + "old_cache_EPSG3857/01/000/000/001/000/000/000.png" + ).check() + assert cache_dir.join( + "new_cache_EPSG3857/00/000/000/000/000/000/000.png" + ).check() - self.created_tiles.append('old_cache_EPSG3857/01/000/000/001/000/000/000.png') - self.created_tiles.append('new_cache_EPSG3857/00/000/000/000/000/000/000.png') - - - def test_get_tile_combined_cache(self): + def test_get_tile_combined_cache(self, app): # request from cache with two cache sources where only one # is compatible (supports tiled_only) expected_reqs = [] - with tmp_image((256, 256), format='jpeg') as img: + with tmp_image((256, 256), format="jpeg") as img: img = img.read() for tile in [ - r'/tiles/04/000/000/008/000/000/011.png', - r'/tiles/04/000/000/008/000/000/010.png', - r'/tiles/utm/00/000/000/000/000/000/000.png', + r"/tiles/04/000/000/008/000/000/011.png", + r"/tiles/04/000/000/008/000/000/010.png", + r"/tiles/utm/00/000/000/000/000/000/000.png", ]: expected_reqs.append( - ({'path': tile}, - {'body': img, 'headers': {'content-type': 'image/png'}})) - - with mock_httpd(('localhost', 42423), expected_reqs, unordered=True): - resp = self.app.get('/tms/1.0.0/combined/EPSG25832/0/0/0.png') - eq_(resp.content_type, 'image/png') - + ( + {"path": tile}, + {"body": img, "headers": {"content-type": "image/png"}}, + ) + ) + + with mock_httpd(("localhost", 42423), expected_reqs, unordered=True): + resp = app.get("/tms/1.0.0/combined/EPSG25832/0/0/0.png") + assert resp.content_type == "image/png" diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_combined_sources.py mapproxy-1.12.0/mapproxy/test/system/test_combined_sources.py --- mapproxy-1.11.0/mapproxy/test/system/test_combined_sources.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_combined_sources.py 2019-08-30 07:34:08.000000000 +0000 @@ -16,236 +16,320 @@ from __future__ import division from io import BytesIO + +import pytest + from mapproxy.request.wms import WMS111MapRequest from mapproxy.compat.image import Image from mapproxy.test.image import is_png, tmp_image, create_tmp_image from mapproxy.test.http import mock_httpd -from mapproxy.test.system import module_setup, module_teardown, SystemTest -from nose.tools import eq_ +from mapproxy.test.system import SysTest -test_config = {} -def setup_module(): - module_setup(test_config, 'combined_sources.yaml') +@pytest.fixture(scope="module") +def config_file(): + return "combined_sources.yaml" -def teardown_module(): - module_teardown(test_config) -class TestCoverageWMS(SystemTest): - config = test_config - def setup(self): - SystemTest.setup(self) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='9,50,10,51', width='200', height='200', - layers='combinable', srs='EPSG:4326', format='image/png', - styles='', request='GetMap')) +class TestCoverageWMS(SysTest): - def test_combined(self): - common_params = (r'?SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0' - '&WIDTH=200&transparent=True') + def setup(self): + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="9,50,10,51", + width="200", + height="200", + layers="combinable", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + ), + ) + + def test_combined(self, app): + common_params = ( + r"?SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0" + "&WIDTH=200&transparent=True" + ) - with tmp_image((200, 200), format='png') as img: + with tmp_image((200, 200), format="png") as img: img = img.read() - expected_req = [({'path': '/service_a' + common_params + '&layers=a_one,a_two,a_three,a_four'}, - {'body': img, 'headers': {'content-type': 'image/png'}}), - ({'path': '/service_b' + common_params + '&layers=b_one'}, - {'body': img, 'headers': {'content-type': 'image/png'}}) - ] - - with mock_httpd(('localhost', 42423), expected_req): - self.common_map_req.params.layers = 'combinable' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + expected_req = [ + ( + { + "path": "/service_a" + + common_params + + "&layers=a_one,a_two,a_three,a_four" + }, + {"body": img, "headers": {"content-type": "image/png"}}, + ), + ( + {"path": "/service_b" + common_params + "&layers=b_one"}, + {"body": img, "headers": {"content-type": "image/png"}}, + ), + ] + + with mock_httpd(("localhost", 42423), expected_req): + self.common_map_req.params.layers = "combinable" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - def test_uncombined(self): - common_params = (r'?SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0' - '&WIDTH=200&transparent=True') + def test_uncombined(self, app): + common_params = ( + r"?SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0" + "&WIDTH=200&transparent=True" + ) - with tmp_image((200, 200), format='png') as img: + with tmp_image((200, 200), format="png") as img: img = img.read() - expected_req = [({'path': '/service_a' + common_params + '&layers=a_one'}, - {'body': img, 'headers': {'content-type': 'image/png'}}), - ({'path': '/service_b' + common_params + '&layers=b_one'}, - {'body': img, 'headers': {'content-type': 'image/png'}}), - ({'path': '/service_a' + common_params + '&layers=a_two,a_three'}, - {'body': img, 'headers': {'content-type': 'image/png'}}) - ] - - with mock_httpd(('localhost', 42423), expected_req): - self.common_map_req.params.layers = 'uncombinable' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + expected_req = [ + ( + {"path": "/service_a" + common_params + "&layers=a_one"}, + {"body": img, "headers": {"content-type": "image/png"}}, + ), + ( + {"path": "/service_b" + common_params + "&layers=b_one"}, + {"body": img, "headers": {"content-type": "image/png"}}, + ), + ( + {"path": "/service_a" + common_params + "&layers=a_two,a_three"}, + {"body": img, "headers": {"content-type": "image/png"}}, + ), + ] + + with mock_httpd(("localhost", 42423), expected_req): + self.common_map_req.params.layers = "uncombinable" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - def test_combined_layers(self): - common_params = (r'?SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0' - '&WIDTH=200&transparent=True') + def test_combined_layers(self, app): + common_params = ( + r"?SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0" + "&WIDTH=200&transparent=True" + ) - with tmp_image((200, 200), format='png') as img: + with tmp_image((200, 200), format="png") as img: img = img.read() expected_req = [ - ({'path': '/service_a' + common_params + '&layers=a_one'}, - {'body': img, 'headers': {'content-type': 'image/png'}}), - ({'path': '/service_b' + common_params + '&layers=b_one'}, - {'body': img, 'headers': {'content-type': 'image/png'}}), - ({'path': '/service_a' + common_params + '&layers=a_two,a_three,a_four'}, - {'body': img, 'headers': {'content-type': 'image/png'}}), - ] - - with mock_httpd(('localhost', 42423), expected_req): - self.common_map_req.params.layers = 'uncombinable,single' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + ( + {"path": "/service_a" + common_params + "&layers=a_one"}, + {"body": img, "headers": {"content-type": "image/png"}}, + ), + ( + {"path": "/service_b" + common_params + "&layers=b_one"}, + {"body": img, "headers": {"content-type": "image/png"}}, + ), + ( + { + "path": "/service_a" + + common_params + + "&layers=a_two,a_three,a_four" + }, + {"body": img, "headers": {"content-type": "image/png"}}, + ), + ] + + with mock_httpd(("localhost", 42423), expected_req): + self.common_map_req.params.layers = "uncombinable,single" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - def test_layers_with_opacity(self): + def test_layers_with_opacity(self, app): # overlay with opacity -> request should not be combined - common_params = (r'?SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0' - '&WIDTH=200') + common_params = ( + r"?SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0" + "&WIDTH=200" + ) img_bg = create_tmp_image((200, 200), color=(0, 0, 0)) img_fg = create_tmp_image((200, 200), color=(255, 0, 128)) expected_req = [ - ({'path': '/service_a' + common_params + '&layers=a_one'}, - {'body': img_bg, 'headers': {'content-type': 'image/png'}}), - ({'path': '/service_a' + common_params + '&layers=a_two'}, - {'body': img_fg, 'headers': {'content-type': 'image/png'}}), - ] - - with mock_httpd(('localhost', 42423), expected_req): - self.common_map_req.params.layers = 'opacity_base,opacity_overlay' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + ( + {"path": "/service_a" + common_params + "&layers=a_one"}, + {"body": img_bg, "headers": {"content-type": "image/png"}}, + ), + ( + {"path": "/service_a" + common_params + "&layers=a_two"}, + {"body": img_fg, "headers": {"content-type": "image/png"}}, + ), + ] + + with mock_httpd(("localhost", 42423), expected_req): + self.common_map_req.params.layers = "opacity_base,opacity_overlay" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) - eq_(img.getcolors()[0], ((200*200),(127, 0, 64))) + assert img.getcolors()[0] == ((200 * 200), (127, 0, 64)) - def test_combined_transp_color(self): + def test_combined_transp_color(self, app): # merged to one request because both layers share the same transparent_color - common_params = (r'?SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0' - '&WIDTH=200&transparent=True') - - with tmp_image((200, 200), color=(255, 0, 0), format='png') as img: - img = img.read() - expected_req = [({'path': '/service_a' + common_params + '&layers=a_iopts_one,a_iopts_two'}, - {'body': img, 'headers': {'content-type': 'image/png'}}), - ] + common_params = ( + r"?SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0" + "&WIDTH=200&transparent=True" + ) - with mock_httpd(('localhost', 42423), expected_req): - self.common_map_req.params.layers = 'layer_image_opts1,layer_image_opts2' + with tmp_image((200, 200), color=(255, 0, 0), format="png") as img: + img = img.read() + expected_req = [ + ( + { + "path": "/service_a" + + common_params + + "&layers=a_iopts_one,a_iopts_two" + }, + {"body": img, "headers": {"content-type": "image/png"}}, + ) + ] + + with mock_httpd(("localhost", 42423), expected_req): + self.common_map_req.params.layers = ( + "layer_image_opts1,layer_image_opts2" + ) self.common_map_req.params.transparent = True - resp = self.app.get(self.common_map_req) - resp.content_type = 'image/png' + resp = app.get(self.common_map_req) + resp.content_type = "image/png" data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) - eq_(img.getcolors()[0], ((200*200),(255, 0, 0, 0))) + assert img.getcolors()[0] == ((200 * 200), (255, 0, 0, 0)) - def test_combined_mixed_transp_color(self): + def test_combined_mixed_transp_color(self, app): # not merged to one request because only one layer has transparent_color - common_params = (r'?SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0' - '&WIDTH=200&transparent=True') - - with tmp_image((200, 200), color=(255, 0, 0), format='png') as img: - img = img.read() - expected_req = [({'path': '/service_a' + common_params + '&layers=a_four'}, - {'body': img, 'headers': {'content-type': 'image/png'}}), - ({'path': '/service_a' + common_params + '&layers=a_iopts_one'}, - {'body': img, 'headers': {'content-type': 'image/png'}}), - ] + common_params = ( + r"?SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0" + "&WIDTH=200&transparent=True" + ) - with mock_httpd(('localhost', 42423), expected_req): - self.common_map_req.params.layers = 'single,layer_image_opts1' + with tmp_image((200, 200), color=(255, 0, 0), format="png") as img: + img = img.read() + expected_req = [ + ( + {"path": "/service_a" + common_params + "&layers=a_four"}, + {"body": img, "headers": {"content-type": "image/png"}}, + ), + ( + {"path": "/service_a" + common_params + "&layers=a_iopts_one"}, + {"body": img, "headers": {"content-type": "image/png"}}, + ), + ] + + with mock_httpd(("localhost", 42423), expected_req): + self.common_map_req.params.layers = "single,layer_image_opts1" self.common_map_req.params.transparent = True - resp = self.app.get(self.common_map_req) - resp.content_type = 'image/png' + resp = app.get(self.common_map_req) + resp.content_type = "image/png" data = BytesIO(resp.body) assert is_png(data) - def test_combined_same_fwd_req_params(self): + def test_combined_same_fwd_req_params(self, app): # merged to one request because all layers share the same time param in # fwd_req_params config - with tmp_image((200, 200), format='png') as img: + with tmp_image((200, 200), format="png") as img: img = img.read() - expected_req = [({'path': '/service_a?SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0' - '&WIDTH=200&transparent=True&TIME=20041012' - '&layers=a_one,a_two,a_three,a_four'}, - {'body': img, 'headers': {'content-type': 'image/png'}}), - ] - - with mock_httpd(('localhost', 42423), expected_req): - self.common_map_req.params.layers = 'layer_fwdparams1,layer_fwdparams2' - self.common_map_req.params['time'] = '20041012' + expected_req = [ + ( + { + "path": "/service_a?SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0" + "&WIDTH=200&transparent=True&TIME=20041012" + "&layers=a_one,a_two,a_three,a_four" + }, + {"body": img, "headers": {"content-type": "image/png"}}, + ) + ] + + with mock_httpd(("localhost", 42423), expected_req): + self.common_map_req.params.layers = "layer_fwdparams1,layer_fwdparams2" + self.common_map_req.params["time"] = "20041012" self.common_map_req.params.transparent = True - resp = self.app.get(self.common_map_req) - resp.content_type = 'image/png' + resp = app.get(self.common_map_req) + resp.content_type = "image/png" data = BytesIO(resp.body) assert is_png(data) - def test_combined_no_fwd_req_params(self): + def test_combined_no_fwd_req_params(self, app): # merged to one request because no vendor param is set - with tmp_image((200, 200), format='png') as img: + with tmp_image((200, 200), format="png") as img: img = img.read() - expected_req = [({'path': '/service_a?SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0' - '&WIDTH=200&transparent=True' - '&layers=a_one,a_two,a_four'}, - {'body': img, 'headers': {'content-type': 'image/png'}}), - ] + expected_req = [ + ( + { + "path": "/service_a?SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0" + "&WIDTH=200&transparent=True" + "&layers=a_one,a_two,a_four" + }, + {"body": img, "headers": {"content-type": "image/png"}}, + ) + ] - with mock_httpd(('localhost', 42423), expected_req): - self.common_map_req.params.layers = 'layer_fwdparams1,single' + with mock_httpd(("localhost", 42423), expected_req): + self.common_map_req.params.layers = "layer_fwdparams1,single" self.common_map_req.params.transparent = True - resp = self.app.get(self.common_map_req) - resp.content_type = 'image/png' + resp = app.get(self.common_map_req) + resp.content_type = "image/png" data = BytesIO(resp.body) assert is_png(data) - def test_combined_mixed_fwd_req_params(self): + def test_combined_mixed_fwd_req_params(self, app): # not merged to one request because fwd_req_params are different - common_params = (r'/service_a?SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0' - '&WIDTH=200&transparent=True') - - with tmp_image((200, 200), format='png') as img: - img = img.read() - expected_req = [({'path': common_params + '&layers=a_one&TIME=20041012'}, - {'body': img, 'headers': {'content-type': 'image/png'}}), - ({'path': common_params + '&layers=a_two&TIME=20041012&VENDOR=foo'}, - {'body': img, 'headers': {'content-type': 'image/png'}}), - ({'path': common_params + '&layers=a_four'}, - {'body': img, 'headers': {'content-type': 'image/png'}}), - ] - - with mock_httpd(('localhost', 42423), expected_req): - self.common_map_req.params.layers = 'layer_fwdparams1,single' - self.common_map_req.params['time'] = '20041012' - self.common_map_req.params['vendor'] = 'foo' + common_params = ( + r"/service_a?SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0" + "&WIDTH=200&transparent=True" + ) + + with tmp_image((200, 200), format="png") as img: + img = img.read() + expected_req = [ + ( + {"path": common_params + "&layers=a_one&TIME=20041012"}, + {"body": img, "headers": {"content-type": "image/png"}}, + ), + ( + {"path": common_params + "&layers=a_two&TIME=20041012&VENDOR=foo"}, + {"body": img, "headers": {"content-type": "image/png"}}, + ), + ( + {"path": common_params + "&layers=a_four"}, + {"body": img, "headers": {"content-type": "image/png"}}, + ), + ] + + with mock_httpd(("localhost", 42423), expected_req): + self.common_map_req.params.layers = "layer_fwdparams1,single" + self.common_map_req.params["time"] = "20041012" + self.common_map_req.params["vendor"] = "foo" self.common_map_req.params.transparent = True - resp = self.app.get(self.common_map_req) - resp.content_type = 'image/png' + resp = app.get(self.common_map_req) + resp.content_type = "image/png" data = BytesIO(resp.body) assert is_png(data) - diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_coverage.py mapproxy-1.12.0/mapproxy/test/system/test_coverage.py --- mapproxy-1.11.0/mapproxy/test/system/test_coverage.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_coverage.py 2019-08-30 07:34:08.000000000 +0000 @@ -16,100 +16,125 @@ from __future__ import division from io import BytesIO + +import pytest + from mapproxy.request.wms import WMS111MapRequest from mapproxy.compat.image import Image from mapproxy.test.image import is_png, tmp_image from mapproxy.test.http import mock_httpd -from mapproxy.test.system import module_setup, module_teardown, SystemTest -from nose.tools import eq_ +from mapproxy.test.system import SysTest + -test_config = {} +@pytest.fixture(scope="module") +def config_file(): + return "coverage.yaml" -def setup_module(): - module_setup(test_config, 'coverage.yaml') -def teardown_module(): - module_teardown(test_config) +class TestCoverageWMS(SysTest): -class TestCoverageWMS(SystemTest): - config = test_config def setup(self): - SystemTest.setup(self) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='-180,0,0,80', width='200', height='200', - layers='wms_cache', srs='EPSG:4326', format='image/png', - styles='', request='GetMap')) + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="-180,0,0,80", + width="200", + height="200", + layers="wms_cache", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + ), + ) - def test_capababilities(self): - resp = self.app.get('/service?request=GetCapabilities&service=WMS&version=1.1.1') + def test_capababilities(self, app): + resp = app.get("/service?request=GetCapabilities&service=WMS&version=1.1.1") xml = resp.lxml # First: combined root, second: wms_cache, third: tms_cache, last: seed_only - eq_(xml.xpath('//LatLonBoundingBox/@minx'), ['10', '10', '12', '14']) - eq_(xml.xpath('//LatLonBoundingBox/@miny'), ['10', '15', '10', '13']) - eq_(xml.xpath('//LatLonBoundingBox/@maxx'), ['35', '30', '35', '24']) - eq_(xml.xpath('//LatLonBoundingBox/@maxy'), ['31', '31', '30', '23']) + assert xml.xpath("//LatLonBoundingBox/@minx") == ["10", "10", "12", "14"] + assert xml.xpath("//LatLonBoundingBox/@miny") == ["10", "15", "10", "13"] + assert xml.xpath("//LatLonBoundingBox/@maxx") == ["35", "30", "35", "24"] + assert xml.xpath("//LatLonBoundingBox/@maxy") == ["31", "31", "30", "23"] - def test_get_map_outside(self): + def test_get_map_outside(self, app): self.common_map_req.params.bbox = -90, 0, 0, 90 - self.common_map_req.params['bgcolor'] = '0xff0005' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + self.common_map_req.params["bgcolor"] = "0xff0005" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) - eq_(img.mode, 'RGB') - eq_(img.getcolors(), [(200*200, (255, 0, 5))]) + assert img.mode == "RGB" + assert img.getcolors() == [(200 * 200, (255, 0, 5))] - def test_get_map_outside_transparent(self): + def test_get_map_outside_transparent(self, app): self.common_map_req.params.bbox = -90, 0, 0, 90 self.common_map_req.params.transparent = True - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) - eq_(img.mode, 'RGBA') - eq_(img.getcolors()[0][0], 200*200) - eq_(img.getcolors()[0][1][3], 0) # transparent - - def test_get_map_intersection(self): - self.created_tiles.append('wms_cache_EPSG4326/03/000/000/004/000/000/002.jpeg') - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg' - '&REQUEST=GetMap&HEIGHT=91&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=10,15,30,31' - '&WIDTH=114'}, - {'body': img.read(), 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req]): + assert img.mode == "RGBA" + assert img.getcolors()[0][0] == 200 * 200 + assert img.getcolors()[0][1][3] == 0 # transparent + + def test_get_map_intersection(self, app, cache_dir): + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg" + "&REQUEST=GetMap&HEIGHT=91&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=10,15,30,31" + "&WIDTH=114" + }, + {"body": img.read(), "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): self.common_map_req.params.bbox = 0, 0, 40, 40 self.common_map_req.params.transparent = True - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - eq_(Image.open(data).mode, 'RGBA') - -class TestCoverageTMS(SystemTest): - config = test_config - - def test_get_tile_intersections(self): - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg' - '&REQUEST=GetMap&HEIGHT=25&SRS=EPSG%3A900913&styles=' - '&VERSION=1.1.1&BBOX=1113194.90793,1689200.13961,3339584.7238,3632749.14338' - '&WIDTH=28'}, - {'body': img.read(), 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - resp = self.app.get('/tms/1.0.0/wms_cache/0/1/1.jpeg') - eq_(resp.content_type, 'image/jpeg') - self.created_tiles.append('wms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg') - - def test_get_tile_intersection_tms(self): - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/tms/1.0.0/foo/1/1/1.jpeg'}, - {'body': img.read(), 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - resp = self.app.get('/tms/1.0.0/tms_cache/0/1/1.jpeg') - eq_(resp.content_type, 'image/jpeg') - self.created_tiles.append('tms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg') - + assert Image.open(data).mode == "RGBA" + assert cache_dir.join( + "wms_cache_EPSG4326/03/000/000/004/000/000/002.jpeg" + ).check() + + +class TestCoverageTMS(SysTest): + + def test_get_tile_intersections(self, app, cache_dir): + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg" + "&REQUEST=GetMap&HEIGHT=25&SRS=EPSG%3A900913&styles=" + "&VERSION=1.1.1&BBOX=1113194.90793,1689200.13961,3339584.7238,3632749.14338" + "&WIDTH=28" + }, + {"body": img.read(), "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + resp = app.get("/tms/1.0.0/wms_cache/0/1/1.jpeg") + assert resp.content_type == "image/jpeg" + cache_dir.join("wms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg").check() + + def test_get_tile_intersection_tms(self, app, cache_dir): + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + {"path": r"/tms/1.0.0/foo/1/1/1.jpeg"}, + {"body": img.read(), "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + resp = app.get("/tms/1.0.0/tms_cache/0/1/1.jpeg") + assert resp.content_type == "image/jpeg" + cache_dir.join("tms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg").check() diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_decorate_img.py mapproxy-1.12.0/mapproxy/test/system/test_decorate_img.py --- mapproxy-1.11.0/mapproxy/test/system/test_decorate_img.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_decorate_img.py 2019-08-30 07:34:08.000000000 +0000 @@ -13,176 +13,202 @@ # See the License for the specific language governing permissions and # limitations under the License. -from mapproxy.test.system import module_setup, module_teardown, SystemTest -from mapproxy.test.system import make_base_config -from mapproxy.test.image import is_png, is_jpeg -from mapproxy.request.wms import WMS111MapRequest -from mapproxy.request.wmts import WMTS100TileRequest from io import BytesIO +import pytest + from mapproxy.compat.image import Image from mapproxy.image import ImageSource -from nose.tools import eq_ - -test_config = {} -base_config = make_base_config(test_config) - - -def setup_module(): - module_setup(test_config, 'layer.yaml', with_cache_data=True) +from mapproxy.request.wms import WMS111MapRequest +from mapproxy.request.wmts import WMTS100TileRequest +from mapproxy.test.image import is_png, is_jpeg +from mapproxy.test.system import SysTest -def teardown_module(): - module_teardown(test_config) +@pytest.fixture(scope="module") +def config_file(): + return "layer.yaml" def to_greyscale(image, service, layers, **kw): img = image.as_image() - if (hasattr(image.image_opts, 'transparent') and - image.image_opts.transparent): - img = img.convert('LA').convert('RGBA') + if hasattr(image.image_opts, "transparent") and image.image_opts.transparent: + img = img.convert("LA").convert("RGBA") else: - img = img.convert('L').convert('RGB') + img = img.convert("L").convert("RGB") return ImageSource(img, image.image_opts) -class TestDecorateImg(SystemTest): - - config = test_config +@pytest.mark.usefixtures("fixture_cache_data") +class TestDecorateImg(SysTest): def setup(self): - SystemTest.setup(self) - self.common_tile_req = WMTS100TileRequest(url='/service?', param=dict(service='WMTS', - version='1.0.0', tilerow='0', tilecol='0', tilematrix='01', tilematrixset='GLOBAL_MERCATOR', - layer='wms_cache', format='image/jpeg', style='', request='GetTile')) + self.common_tile_req = WMTS100TileRequest( + url="/service?", + param=dict( + service="WMTS", + version="1.0.0", + tilerow="0", + tilecol="0", + tilematrix="01", + tilematrixset="GLOBAL_MERCATOR", + layer="wms_cache", + format="image/jpeg", + style="", + request="GetTile", + ), + ) - def test_wms(self): + def test_wms(self, app): req = WMS111MapRequest( - url='/service?', + url="/service?", param=dict( - service='WMS', - version='1.1.1', bbox='-180,0,0,80', width='200', height='200', - layers='wms_cache', srs='EPSG:4326', format='image/png', - styles='', request='GetMap' - ) - ) - resp = self.app.get( - req, - extra_environ={'mapproxy.decorate_img': to_greyscale} + service="WMS", + version="1.1.1", + bbox="-180,0,0,80", + width="200", + height="200", + layers="wms_cache", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + ), ) + resp = app.get(req, extra_environ={"mapproxy.decorate_img": to_greyscale}) data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) - eq_(img.mode, 'RGB') + assert img.mode == "RGB" - def test_wms_transparent(self): + def test_wms_transparent(self, app): req = WMS111MapRequest( - url='/service?', + url="/service?", param=dict( - service='WMS', version='1.1.1', bbox='-180,0,0,80', - width='200', height='200', layers='wms_cache_transparent', - srs='EPSG:4326', format='image/png', - styles='', request='GetMap', transparent='True' - ) - ) - resp = self.app.get( - req, extra_environ={'mapproxy.decorate_img': to_greyscale} + service="WMS", + version="1.1.1", + bbox="-180,0,0,80", + width="200", + height="200", + layers="wms_cache_transparent", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + transparent="True", + ), ) + resp = app.get(req, extra_environ={"mapproxy.decorate_img": to_greyscale}) data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) - eq_(img.mode, 'RGBA') + assert img.mode == "RGBA" - def test_wms_bgcolor(self): + def test_wms_bgcolor(self, app): req = WMS111MapRequest( - url='/service?', + url="/service?", param=dict( - service='WMS', version='1.1.1', bbox='-180,0,0,80', - width='200', height='200', layers='wms_cache_transparent', - srs='EPSG:4326', format='image/png', - styles='', request='GetMap', bgcolor='0xff00a0' - ) - ) - resp = self.app.get( - req, extra_environ={'mapproxy.decorate_img': to_greyscale} + service="WMS", + version="1.1.1", + bbox="-180,0,0,80", + width="200", + height="200", + layers="wms_cache_transparent", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + bgcolor="0xff00a0", + ), ) + resp = app.get(req, extra_environ={"mapproxy.decorate_img": to_greyscale}) data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) - eq_(img.mode, 'RGB') - eq_(sorted(img.getcolors())[-1][1], (94, 94, 94)) + assert img.mode == "RGB" + assert sorted(img.getcolors())[-1][1] == (94, 94, 94) - def test_wms_args(self): + def test_wms_args(self, app): req = WMS111MapRequest( - url='/service?', + url="/service?", param=dict( - service='WMS', version='1.1.1', bbox='-180,0,0,80', - width='200', height='200', layers='wms_cache,wms_cache_transparent', - srs='EPSG:4326', format='image/png', - styles='', request='GetMap', transparent='True' - ) + service="WMS", + version="1.1.1", + bbox="-180,0,0,80", + width="200", + height="200", + layers="wms_cache,wms_cache_transparent", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + transparent="True", + ), ) + def callback(img_src, service, layers, environ, query_extent): assert isinstance(img_src, ImageSource) - eq_('wms.map', service) - eq_(len(layers), 2) - assert 'wms_cache_transparent' in layers - assert 'wms_cache' in layers + assert "wms.map" == service + assert len(layers) == 2 + assert "wms_cache_transparent" in layers + assert "wms_cache" in layers assert isinstance(environ, dict) assert len(query_extent) == 2 assert len(query_extent[1]) == 4 - eq_(query_extent[0], 'EPSG:4326') + assert query_extent[0] == "EPSG:4326" return img_src - resp = self.app.get( - req, extra_environ={'mapproxy.decorate_img': callback} - ) - def test_tms(self): - resp = self.app.get( - '/tms/1.0.0/wms_cache/0/0/1.jpeg', - extra_environ={'mapproxy.decorate_img': to_greyscale} + app.get(req, extra_environ={"mapproxy.decorate_img": callback}) + + def test_tms(self, app): + resp = app.get( + "/tms/1.0.0/wms_cache/0/0/1.jpeg", + extra_environ={"mapproxy.decorate_img": to_greyscale}, ) - eq_(resp.content_type, 'image/jpeg') - eq_(resp.content_length, len(resp.body)) + assert resp.content_type == "image/jpeg" + assert resp.content_length == len(resp.body) data = BytesIO(resp.body) assert is_jpeg(data) - def test_tms_args(self): + def test_tms_args(self, app): + def callback(img_src, service, layers, environ, query_extent): assert isinstance(img_src, ImageSource) - eq_('tms', service) - eq_('wms_cache', layers[0]) + assert "tms" == service + assert "wms_cache" == layers[0] assert isinstance(environ, dict) assert len(query_extent) == 2 assert len(query_extent[1]) == 4 - eq_(query_extent[0], 'EPSG:900913') + assert query_extent[0] == "EPSG:900913" return img_src - resp = self.app.get( - '/tms/1.0.0/wms_cache/0/0/1.jpeg', - extra_environ={'mapproxy.decorate_img': callback} + + app.get( + "/tms/1.0.0/wms_cache/0/0/1.jpeg", + extra_environ={"mapproxy.decorate_img": callback}, ) - def test_wmts(self): - resp = self.app.get( + def test_wmts(self, app): + resp = app.get( str(self.common_tile_req), - extra_environ={'mapproxy.decorate_img': to_greyscale} + extra_environ={"mapproxy.decorate_img": to_greyscale}, ) - eq_(resp.content_type, 'image/jpeg') - eq_(resp.content_length, len(resp.body)) + assert resp.content_type == "image/jpeg" + assert resp.content_length == len(resp.body) data = BytesIO(resp.body) assert is_jpeg(data) - def test_wmts_args(self): + def test_wmts_args(self, app): + def callback(img_src, service, layers, environ, query_extent): assert isinstance(img_src, ImageSource) - eq_('wmts', service) - eq_('wms_cache', layers[0]) + assert "wmts" == service + assert "wms_cache" == layers[0] assert isinstance(environ, dict) assert len(query_extent) == 2 assert len(query_extent[1]) == 4 - eq_(query_extent[0], 'EPSG:900913') + assert query_extent[0] == "EPSG:900913" return img_src - resp = self.app.get( - str(self.common_tile_req), - extra_environ={'mapproxy.decorate_img': callback} + + app.get( + str(self.common_tile_req), extra_environ={"mapproxy.decorate_img": callback} ) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_disable_storage.py mapproxy-1.12.0/mapproxy/test/system/test_disable_storage.py --- mapproxy-1.11.0/mapproxy/test/system/test_disable_storage.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_disable_storage.py 2019-08-30 07:34:08.000000000 +0000 @@ -1,53 +1,42 @@ # This file is part of the MapProxy project. # Copyright (C) 2011 Omniscale -# +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -import os +import pytest from mapproxy.test.image import is_png, tmp_image from mapproxy.test.http import mock_httpd -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config -from nose.tools import eq_ +from mapproxy.test.system import SysTest -test_config = {} -base_config = make_base_config(test_config) -def setup_module(): - module_setup(test_config, 'disable_storage.yaml', with_cache_data=False) +@pytest.fixture(scope="module") +def config_file(): + return "disable_storage.yaml" -def teardown_module(): - module_teardown(test_config) - -class TestDisableStorage(SystemTest): - config = test_config - - def test_get_tile_without_caching(self): - with tmp_image((256, 256), format='png') as img: - expected_req = ({'path': r'/tile.png'}, - {'body': img.read(), 'headers': {'content-type': 'image/png'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - resp = self.app.get('/tms/1.0.0/tiles/0/0/0.png') - eq_(resp.content_type, 'image/png') - is_png(resp.body) - - assert not os.path.exists(test_config['cache_dir']) - - with tmp_image((256, 256), format='png') as img: - expected_req = ({'path': r'/tile.png'}, - {'body': img.read(), 'headers': {'content-type': 'image/png'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - resp = self.app.get('/tms/1.0.0/tiles/0/0/0.png') - eq_(resp.content_type, 'image/png') - is_png(resp.body) - \ No newline at end of file + +class TestDisableStorage(SysTest): + + def test_get_tile_without_caching(self, app, cache_dir): + for _ in range(2): + with tmp_image((256, 256), format="png") as img: + expected_req = ( + {"path": r"/tile.png"}, + {"body": img.read(), "headers": {"content-type": "image/png"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + resp = app.get("/tms/1.0.0/tiles/0/0/0.png") + assert resp.content_type == "image/png" + is_png(resp.body) + + assert not cache_dir.check() diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_formats.py mapproxy-1.12.0/mapproxy/test/system/test_formats.py --- mapproxy-1.11.0/mapproxy/test/system/test_formats.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_formats.py 2019-08-30 07:34:08.000000000 +0000 @@ -14,151 +14,206 @@ # limitations under the License. from __future__ import division -import os + from io import BytesIO + +import pytest + from mapproxy.request.wms import WMS111MapRequest, WMS111FeatureInfoRequest from mapproxy.test.image import tmp_image, check_format from mapproxy.test.http import mock_httpd -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config -from nose.tools import eq_ - -test_config = {} -base_config = make_base_config(test_config) +from mapproxy.test.system import SysTest -def setup_module(): - module_setup(test_config, 'formats.yaml', with_cache_data=True) -def teardown_module(): - module_teardown(test_config) +@pytest.fixture(scope="module") +def config_file(): + return "formats.yaml" -class TilesTest(SystemTest): - config = test_config - def created_tiles_filenames(self): - base_dir = base_config().cache.base_dir - for filename, format in self.created_tiles: - yield os.path.join(base_dir, filename), format +def assert_file_format(filename, format): + assert filename.check() + check_format(filename.read_binary(), format) - def _test_created_tiles(self): - for filename, format in self.created_tiles_filenames(): - if not os.path.exists(filename): - assert False, "didn't found tile " + filename - else: - check_format(open(filename, 'rb'), format) - def teardown(self): - self._test_created_tiles() - for filename, _format in self.created_tiles_filenames(): - if os.path.exists(filename): - os.remove(filename) +class TestWMS111(SysTest): - -class TestWMS111(TilesTest): def setup(self): - TilesTest.setup(self) - self.common_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1')) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='0,0,180,80', width='200', height='200', - layers='wms_cache', srs='EPSG:4326', format='image/png', - styles='', request='GetMap')) - self.common_direct_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='0,0,10,10', width='200', height='200', - layers='wms_cache', srs='EPSG:4326', format='image/png', - styles='', request='GetMap')) - self.common_fi_req = WMS111FeatureInfoRequest(url='/service?', - param=dict(x='10', y='20', width='200', height='200', layers='wms_cache', - format='image/png', query_layers='wms_cache', styles='', - bbox='1000,400,2000,1400', srs='EPSG:900913')) - self.expected_base_path = '/service?SERVICE=WMS&REQUEST=GetMap&HEIGHT=256' \ - '&SRS=EPSG%3A900913&styles=&VERSION=1.1.1&WIDTH=256' \ - '&BBOX=0.0,0.0,20037508.3428,20037508.3428' - self.expected_direct_base_path = '/service?SERVICE=WMS&REQUEST=GetMap&HEIGHT=200' \ - '&SRS=EPSG%3A4326&styles=&VERSION=1.1.1&WIDTH=200' \ - '&BBOX=0.0,0.0,10.0,10.0' - - def test_cache_formats(self): - yield self.check_get_cached, 'jpeg_cache_tiff_source', 'tiffsource', 'png', 'jpeg', 'tiff' - yield self.check_get_cached, 'jpeg_cache_tiff_source', 'tiffsource', 'jpeg', 'jpeg', 'tiff' - yield self.check_get_cached, 'jpeg_cache_tiff_source', 'tiffsource', 'tiff', 'jpeg', 'tiff' - yield self.check_get_cached, 'jpeg_cache_tiff_source', 'tiffsource', 'gif', 'jpeg', 'tiff' - - yield self.check_get_cached, 'png_cache_all_source', 'allsource', 'png', 'png', 'png' - yield self.check_get_cached, 'png_cache_all_source', 'allsource', 'jpeg', 'png', 'png' - - yield self.check_get_cached, 'jpeg_cache_png_jpeg_source', 'pngjpegsource', 'jpeg', 'jpeg', 'jpeg' - yield self.check_get_cached, 'jpeg_cache_png_jpeg_source', 'pngjpegsource', 'png', 'jpeg', 'jpeg' - - def test_direct_formats(self): - yield self.check_get_direct, 'jpeg_cache_tiff_source', 'tiffsource', 'gif', 'tiff' - yield self.check_get_direct, 'jpeg_cache_tiff_source', 'tiffsource', 'jpeg', 'tiff' - yield self.check_get_direct, 'jpeg_cache_tiff_source', 'tiffsource', 'png', 'tiff' - - yield self.check_get_direct, 'png_cache_all_source', 'allsource', 'gif', 'gif' - yield self.check_get_direct, 'png_cache_all_source', 'allsource', 'png', 'png' - yield self.check_get_direct, 'png_cache_all_source', 'allsource', 'tiff', 'tiff' - - yield self.check_get_direct, 'jpeg_cache_png_jpeg_source', 'pngjpegsource', 'jpeg', 'jpeg' - yield self.check_get_direct, 'jpeg_cache_png_jpeg_source', 'pngjpegsource', 'png', 'png' - yield self.check_get_direct, 'jpeg_cache_png_jpeg_source', 'pngjpegsource', 'tiff', 'png' - yield self.check_get_direct, 'jpeg_cache_png_jpeg_source', 'pngjpegsource', 'gif', 'png' - - - def check_get_cached(self, layer, source, wms_format, cache_format, req_format): - self.created_tiles.append((layer+'_EPSG900913/01/000/000/001/000/000/001.'+cache_format, cache_format)) + self.common_req = WMS111MapRequest( + url="/service?", param=dict(service="WMS", version="1.1.1") + ) + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="0,0,180,80", + width="200", + height="200", + layers="wms_cache", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + ), + ) + self.common_direct_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="0,0,10,10", + width="200", + height="200", + layers="wms_cache", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + ), + ) + self.common_fi_req = WMS111FeatureInfoRequest( + url="/service?", + param=dict( + x="10", + y="20", + width="200", + height="200", + layers="wms_cache", + format="image/png", + query_layers="wms_cache", + styles="", + bbox="1000,400,2000,1400", + srs="EPSG:900913", + ), + ) + self.expected_base_path = "/service?SERVICE=WMS&REQUEST=GetMap&HEIGHT=256" "&SRS=EPSG%3A900913&styles=&VERSION=1.1.1&WIDTH=256" "&BBOX=0.0,0.0,20037508.3428,20037508.3428" + self.expected_direct_base_path = "/service?SERVICE=WMS&REQUEST=GetMap&HEIGHT=200" "&SRS=EPSG%3A4326&styles=&VERSION=1.1.1&WIDTH=200" "&BBOX=0.0,0.0,10.0,10.0" + + @pytest.mark.parametrize( + "layer,source,wms_format,cache_format,req_format", + [ + ["jpeg_cache_tiff_source", "tiffsource", "png", "jpeg", "tiff"], + ["jpeg_cache_tiff_source", "tiffsource", "jpeg", "jpeg", "tiff"], + ["jpeg_cache_tiff_source", "tiffsource", "tiff", "jpeg", "tiff"], + ["jpeg_cache_tiff_source", "tiffsource", "gif", "jpeg", "tiff"], + ["png_cache_all_source", "allsource", "png", "png", "png"], + ["png_cache_all_source", "allsource", "jpeg", "png", "png"], + ["jpeg_cache_png_jpeg_source", "pngjpegsource", "jpeg", "jpeg", "jpeg"], + ["jpeg_cache_png_jpeg_source", "pngjpegsource", "png", "jpeg", "jpeg"], + ], + ) + def test_get_cached( + self, app, cache_dir, layer, source, wms_format, cache_format, req_format + ): with tmp_image((256, 256), format=req_format) as img: - expected_req = ({'path': self.expected_base_path + - '&layers=' + source + - '&format=image%2F' + req_format}, - {'body': img.read(), 'headers': {'content-type': 'image/'+req_format}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - self.common_map_req.params['layers'] = layer - self.common_map_req.params['format'] = 'image/'+wms_format - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/'+wms_format) + expected_req = ( + { + "path": self.expected_base_path + + "&layers=" + + source + + "&format=image%2F" + + req_format + }, + { + "body": img.read(), + "headers": {"content-type": "image/" + req_format}, + }, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + self.common_map_req.params["layers"] = layer + self.common_map_req.params["format"] = "image/" + wms_format + resp = app.get(self.common_map_req) + assert resp.content_type == "image/" + wms_format check_format(BytesIO(resp.body), wms_format) - - - def check_get_direct(self, layer, source, wms_format, req_format): + assert_file_format( + cache_dir.join( + layer + "_EPSG900913/01/000/000/001/000/000/001." + cache_format + ), + cache_format, + ) + + @pytest.mark.parametrize( + "layer,source,wms_format,req_format", + [ + ["jpeg_cache_tiff_source", "tiffsource", "gif", "tiff"], + ["jpeg_cache_tiff_source", "tiffsource", "jpeg", "tiff"], + ["jpeg_cache_tiff_source", "tiffsource", "png", "tiff"], + ["png_cache_all_source", "allsource", "gif", "gif"], + ["png_cache_all_source", "allsource", "png", "png"], + ["png_cache_all_source", "allsource", "tiff", "tiff"], + ["jpeg_cache_png_jpeg_source", "pngjpegsource", "jpeg", "jpeg"], + ["jpeg_cache_png_jpeg_source", "pngjpegsource", "png", "png"], + ["jpeg_cache_png_jpeg_source", "pngjpegsource", "tiff", "png"], + ["jpeg_cache_png_jpeg_source", "pngjpegsource", "gif", "png"], + ], + ) + def test_get_direct(self, app, layer, source, wms_format, req_format): with tmp_image((256, 256), format=req_format) as img: - expected_req = ({'path': self.expected_direct_base_path + - '&layers=' + source + - '&format=image%2F' + req_format}, - {'body': img.read(), 'headers': {'content-type': 'image/'+req_format}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - self.common_direct_map_req.params['layers'] = layer - self.common_direct_map_req.params['format'] = 'image/'+wms_format - resp = self.app.get(self.common_direct_map_req) - eq_(resp.content_type, 'image/'+wms_format) + expected_req = ( + { + "path": self.expected_direct_base_path + + "&layers=" + + source + + "&format=image%2F" + + req_format + }, + { + "body": img.read(), + "headers": {"content-type": "image/" + req_format}, + }, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + self.common_direct_map_req.params["layers"] = layer + self.common_direct_map_req.params["format"] = "image/" + wms_format + resp = app.get(self.common_direct_map_req) + assert resp.content_type == "image/" + wms_format check_format(BytesIO(resp.body), wms_format) -class TestTMS(TilesTest): - def setup(self): - TilesTest.setup(self) - self.expected_base_path = '/service?SERVICE=WMS&REQUEST=GetMap&HEIGHT=256' \ - '&SRS=EPSG%3A900913&styles=&VERSION=1.1.1&WIDTH=256' \ - '&BBOX=0.0,0.0,20037508.3428,20037508.3428' - self.expected_direct_base_path = '/service?SERVICE=WMS&REQUEST=GetMap&HEIGHT=200' \ - '&SRS=EPSG%3A4326&styles=&VERSION=1.1.1&WIDTH=200' \ - '&BBOX=0.0,0.0,10.0,10.0' - - def test_cache_formats(self): - yield self.check_get_cached, 'jpeg_cache_tiff_source', 'tiffsource', 'jpeg', 'jpeg', 'tiff' - - yield self.check_get_cached, 'png_cache_all_source', 'allsource', 'png', 'png', 'png' - - yield self.check_get_cached, 'jpeg_cache_png_jpeg_source', 'pngjpegsource', 'jpeg', 'jpeg', 'jpeg' +class TestTMS(SysTest): + def setup(self): + self.expected_base_path = "/service?SERVICE=WMS&REQUEST=GetMap&HEIGHT=256" "&SRS=EPSG%3A900913&styles=&VERSION=1.1.1&WIDTH=256" "&BBOX=0.0,0.0,20037508.3428,20037508.3428" + self.expected_direct_base_path = "/service?SERVICE=WMS&REQUEST=GetMap&HEIGHT=200" "&SRS=EPSG%3A4326&styles=&VERSION=1.1.1&WIDTH=200" "&BBOX=0.0,0.0,10.0,10.0" - def check_get_cached(self, layer, source, tms_format, cache_format, req_format): - self.created_tiles.append((layer+'_EPSG900913/01/000/000/001/000/000/001.'+cache_format, cache_format)) + @pytest.mark.parametrize( + "layer,source,tms_format,cache_format,req_format", + [ + ["jpeg_cache_tiff_source", "tiffsource", "jpeg", "jpeg", "tiff"], + ["png_cache_all_source", "allsource", "png", "png", "png"], + ["jpeg_cache_png_jpeg_source", "pngjpegsource", "jpeg", "jpeg", "jpeg"], + ], + ) + def test_get_cached( + self, app, cache_dir, layer, source, tms_format, cache_format, req_format + ): with tmp_image((256, 256), format=req_format) as img: - expected_req = ({'path': self.expected_base_path + - '&layers=' + source + - '&format=image%2F' + req_format}, - {'body': img.read(), 'headers': {'content-type': 'image/'+req_format}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - resp = self.app.get('/tms/1.0.0/%s/0/1/1.%s' % (layer, tms_format)) - eq_(resp.content_type, 'image/'+tms_format) + expected_req = ( + { + "path": self.expected_base_path + + "&layers=" + + source + + "&format=image%2F" + + req_format + }, + { + "body": img.read(), + "headers": {"content-type": "image/" + req_format}, + }, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + resp = app.get("/tms/1.0.0/%s/0/1/1.%s" % (layer, tms_format)) + assert resp.content_type == "image/" + tms_format # check_format(BytesIO(resp.body), tms_format) + assert_file_format( + cache_dir.join( + layer + "_EPSG900913/01/000/000/001/000/000/001." + cache_format + ), + cache_format, + ) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_inspire_vs.py mapproxy-1.12.0/mapproxy/test/system/test_inspire_vs.py --- mapproxy-1.11.0/mapproxy/test/system/test_inspire_vs.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_inspire_vs.py 2019-08-30 07:34:08.000000000 +0000 @@ -17,124 +17,157 @@ import functools +import pytest + from mapproxy.request.wms import WMS130CapabilitiesRequest from mapproxy.test.helper import validate_with_xsd -from nose.tools import eq_ - -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config - -test_config = {} -test_linked_config = {} -base_config = make_base_config(test_config) - -def setup_module(): - module_setup(test_linked_config, 'inspire.yaml', with_cache_data=True) - module_setup(test_config, 'inspire_full.yaml', with_cache_data=True) +from mapproxy.test.system import SysTest -def teardown_module(): - module_teardown(test_linked_config) - module_teardown(test_config) def is_inpire_vs_capa(xml): - return validate_with_xsd(xml, xsd_name='inspire/inspire_vs/1.0/inspire_vs.xsd') + return validate_with_xsd(xml, xsd_name="inspire/inspire_vs/1.0/inspire_vs.xsd") def bbox_srs_from_boundingbox(bbox_elem): return [ - float(bbox_elem.attrib['minx']), - float(bbox_elem.attrib['miny']), - float(bbox_elem.attrib['maxx']), - float(bbox_elem.attrib['maxy']), + float(bbox_elem.attrib["minx"]), + float(bbox_elem.attrib["miny"]), + float(bbox_elem.attrib["maxx"]), + float(bbox_elem.attrib["maxy"]), ] -ns130 = {'wms': 'http://www.opengis.net/wms', - 'ogc': 'http://www.opengis.net/ogc', - 'sld': 'http://www.opengis.net/sld', - 'xlink': 'http://www.w3.org/1999/xlink', - 'ic': 'http://inspire.ec.europa.eu/schemas/common/1.0', - 'iv': 'http://inspire.ec.europa.eu/schemas/inspire_vs/1.0', + + +ns130 = { + "wms": "http://www.opengis.net/wms", + "ogc": "http://www.opengis.net/ogc", + "sld": "http://www.opengis.net/sld", + "xlink": "http://www.w3.org/1999/xlink", + "ic": "http://inspire.ec.europa.eu/schemas/common/1.0", + "iv": "http://inspire.ec.europa.eu/schemas/inspire_vs/1.0", } -def eq_xpath(xml, xpath, expected, namespaces=None): + +def assert_xpath(xml, xpath, expected, namespaces=None): elems = xml.xpath(xpath, namespaces=namespaces) assert len(elems) == 1, elems - eq_(elems[0], expected) + assert elems[0] == expected + def xpath_130(xml, xpath): return xml.xpath(xpath, namespaces=ns130) -eq_xpath_wms130 = functools.partial(eq_xpath, namespaces=ns130) -class TestLinkedMD(SystemTest): - config = test_linked_config +assert_xpath_wms130 = functools.partial(assert_xpath, namespaces=ns130) - def setup(self): - SystemTest.setup(self) - def test_wms_capabilities(self): - req = WMS130CapabilitiesRequest(url='/service?') - resp = self.app.get(req) - eq_(resp.content_type, 'text/xml') +class TestLinkedMD(SysTest): + + @pytest.fixture(scope="class") + def config_file(self): + return "inspire.yaml" + + def test_wms_capabilities(self, app): + req = WMS130CapabilitiesRequest(url="/service?") + resp = app.get(req) + assert resp.content_type == "text/xml" print(resp.body) xml = resp.lxml assert is_inpire_vs_capa(xml) - ext_cap =xpath_130(xml, '/wms:WMS_Capabilities/wms:Capability/iv:ExtendedCapabilities') + ext_cap = xpath_130( + xml, "/wms:WMS_Capabilities/wms:Capability/iv:ExtendedCapabilities" + ) assert len(ext_cap) == 1, ext_cap ext_cap = ext_cap[0] - eq_xpath_wms130(ext_cap, './ic:MetadataUrl/ic:URL/text()', u'http://example.org/metadata') - eq_xpath_wms130(ext_cap, './ic:MetadataUrl/ic:MediaType/text()', u'application/vnd.iso.19139+xml') - - eq_xpath_wms130(ext_cap, './ic:SupportedLanguages/ic:DefaultLanguage/ic:Language/text()', u'eng') - eq_xpath_wms130(ext_cap, './ic:ResponseLanguage/ic:Language/text()', u'eng') + assert_xpath_wms130( + ext_cap, "./ic:MetadataUrl/ic:URL/text()", u"http://example.org/metadata" + ) + assert_xpath_wms130( + ext_cap, + "./ic:MetadataUrl/ic:MediaType/text()", + u"application/vnd.iso.19139+xml", + ) + + assert_xpath_wms130( + ext_cap, + "./ic:SupportedLanguages/ic:DefaultLanguage/ic:Language/text()", + u"eng", + ) + assert_xpath_wms130(ext_cap, "./ic:ResponseLanguage/ic:Language/text()", u"eng") # test for extended layer metadata - eq_xpath_wms130(xml, '/wms:WMS_Capabilities/wms:Capability/wms:Layer/wms:Attribution/wms:Title/text()', - u'My attribution title') - - layer_names = set(xml.xpath('//wms:Layer/wms:Name/text()', - namespaces=ns130)) - expected_names = set(['inspire_example']) - eq_(layer_names, expected_names) - -class TestFullMD(SystemTest): - config = test_config - - def setup(self): - SystemTest.setup(self) - - def test_wms_capabilities(self): - req = WMS130CapabilitiesRequest(url='/service?') - resp = self.app.get(req) - eq_(resp.content_type, 'text/xml') + assert_xpath_wms130( + xml, + "/wms:WMS_Capabilities/wms:Capability/wms:Layer/wms:Attribution/wms:Title/text()", + u"My attribution title", + ) + + layer_names = set(xml.xpath("//wms:Layer/wms:Name/text()", namespaces=ns130)) + expected_names = set(["inspire_example"]) + assert layer_names == expected_names + + +class TestFullMD(SysTest): + + @pytest.fixture(scope="class") + def config_file(self): + return "inspire_full.yaml" + + def test_wms_capabilities(self, app): + req = WMS130CapabilitiesRequest(url="/service?") + resp = app.get(req) + assert resp.content_type == "text/xml" print(resp.body) xml = resp.lxml assert is_inpire_vs_capa(xml) - ext_cap =xpath_130(xml, '/wms:WMS_Capabilities/wms:Capability/iv:ExtendedCapabilities') + ext_cap = xpath_130( + xml, "/wms:WMS_Capabilities/wms:Capability/iv:ExtendedCapabilities" + ) assert len(ext_cap) == 1, ext_cap ext_cap = ext_cap[0] - eq_xpath_wms130(ext_cap, './ic:ResourceLocator/ic:URL/text()', u'http://example.org/metadata') - eq_xpath_wms130(ext_cap, './ic:ResourceLocator/ic:MediaType/text()', u'application/vnd.iso.19139+xml') - - eq_xpath_wms130(ext_cap, './ic:Keyword/ic:OriginatingControlledVocabulary/ic:Title/text()', u'GEMET - INSPIRE themes') - - eq_xpath_wms130(ext_cap, './ic:SupportedLanguages/ic:DefaultLanguage/ic:Language/text()', u'eng') - eq_xpath_wms130(ext_cap, './ic:ResponseLanguage/ic:Language/text()', u'eng') + assert_xpath_wms130( + ext_cap, + "./ic:ResourceLocator/ic:URL/text()", + u"http://example.org/metadata", + ) + assert_xpath_wms130( + ext_cap, + "./ic:ResourceLocator/ic:MediaType/text()", + u"application/vnd.iso.19139+xml", + ) + + assert_xpath_wms130( + ext_cap, + "./ic:Keyword/ic:OriginatingControlledVocabulary/ic:Title/text()", + u"GEMET - INSPIRE themes", + ) + + assert_xpath_wms130( + ext_cap, + "./ic:SupportedLanguages/ic:DefaultLanguage/ic:Language/text()", + u"eng", + ) + assert_xpath_wms130(ext_cap, "./ic:ResponseLanguage/ic:Language/text()", u"eng") # check dates from string and datetime - eq_xpath_wms130(ext_cap, './ic:TemporalReference/ic:DateOfCreation/text()', u'2015-05-01') - eq_xpath_wms130(ext_cap, './ic:MetadataDate/text()', u'2015-07-23') + assert_xpath_wms130( + ext_cap, "./ic:TemporalReference/ic:DateOfCreation/text()", u"2015-05-01" + ) + assert_xpath_wms130(ext_cap, "./ic:MetadataDate/text()", u"2015-07-23") # test for extended layer metadata - eq_xpath_wms130(xml, '/wms:WMS_Capabilities/wms:Capability/wms:Layer/wms:Attribution/wms:Title/text()', - u'My attribution title') - - layer_names = set(xml.xpath('//wms:Layer/wms:Name/text()', - namespaces=ns130)) - expected_names = set(['inspire_example']) - eq_(layer_names, expected_names) + assert_xpath_wms130( + xml, + "/wms:WMS_Capabilities/wms:Capability/wms:Layer/wms:Attribution/wms:Title/text()", + u"My attribution title", + ) + + layer_names = set(xml.xpath("//wms:Layer/wms:Name/text()", namespaces=ns130)) + expected_names = set(["inspire_example"]) + assert layer_names == expected_names diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_kml.py mapproxy-1.12.0/mapproxy/test/system/test_kml.py --- mapproxy-1.11.0/mapproxy/test/system/test_kml.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_kml.py 2019-08-30 07:34:08.000000000 +0000 @@ -15,221 +15,248 @@ import os import hashlib + from io import BytesIO + +import pytest + from mapproxy.srs import bbox_equals from mapproxy.util.times import format_httpdate from mapproxy.test.image import is_jpeg, tmp_image from mapproxy.test.http import mock_httpd from mapproxy.test.helper import validate_with_xsd -from nose.tools import eq_ +from mapproxy.test.system import SysTest -ns = {'kml': 'http://www.opengis.net/kml/2.2'} -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config +@pytest.fixture(scope="module") +def config_file(): + return "kml_layer.yaml" -test_config = {} -base_config = make_base_config(test_config) -def setup_module(): - module_setup(test_config, 'kml_layer.yaml', with_cache_data=True) +ns = {"kml": "http://www.opengis.net/kml/2.2"} -def teardown_module(): - module_teardown(test_config) -class TestKML(SystemTest): - config = test_config +class TestKML(SysTest): - def test_get_out_of_bounds_tile(self): + def test_get_out_of_bounds_tile(self, app): for coord in [(0, 0, -1), (-1, 0, 0), (0, -1, 0), (4, 2, 1), (1, 3, 0)]: - yield self.check_out_of_bounds, coord - - def check_out_of_bounds(self, coord): - x, y, z = coord - url = '/kml/wms_cache/%d/%d/%d.kml' % (z, x, y) - resp = self.app.get(url , status=404) - assert 'outside the bounding box' in resp - - def test_invalid_layer(self): - resp = self.app.get('/kml/inVAlid/0/0/0.png', status=404) - eq_(resp.content_type, 'text/plain') - assert 'unknown layer: inVAlid' in resp - - def test_invalid_format(self): - resp = self.app.get('/kml/wms_cache/0/0/1.png', status=404) - eq_(resp.content_type, 'text/plain') - assert 'invalid format' in resp - - def test_get_tile_tile_source_error(self): - resp = self.app.get('/kml/wms_cache/0/0/0.jpeg', status=500) - eq_(resp.content_type, 'text/plain') - assert 'No response from URL' in resp + x, y, z = coord + url = "/kml/wms_cache/%d/%d/%d.kml" % (z, x, y) + resp = app.get(url, status=404) + assert "outside the bounding box" in resp + + def test_invalid_layer(self, app): + resp = app.get("/kml/inVAlid/0/0/0.png", status=404) + assert resp.content_type == "text/plain" + assert "unknown layer: inVAlid" in resp + + def test_invalid_format(self, app): + resp = app.get("/kml/wms_cache/0/0/1.png", status=404) + assert resp.content_type == "text/plain" + assert "invalid format" in resp + + def test_get_tile_tile_source_error(self, app): + resp = app.get("/kml/wms_cache/0/0/0.jpeg", status=500) + assert resp.content_type == "text/plain" + assert "No response from URL" in resp def _check_tile_resp(self, resp): - eq_(resp.content_type, 'image/jpeg') - eq_(resp.content_length, len(resp.body)) + assert resp.content_type == "image/jpeg" + assert resp.content_length == len(resp.body) data = BytesIO(resp.body) assert is_jpeg(data) - def _update_timestamp(self): + def _update_timestamp(self, base_config, cache_dir): timestamp = 1234567890.0 size = 10214 - base_dir = base_config().cache.base_dir - os.utime(os.path.join(base_dir, - 'wms_cache_EPSG900913/01/000/000/000/000/000/001.jpeg'), - (timestamp, timestamp)) - max_age = base_config().tiles.expires_hours * 60 * 60 - etag = hashlib.md5((str(timestamp) + str(size)).encode('ascii')).hexdigest() + os.utime( + cache_dir.join( + "wms_cache_EPSG900913/01/000/000/000/000/000/001.jpeg" + ).strpath, + (timestamp, timestamp), + ) + max_age = base_config.tiles.expires_hours * 60 * 60 + etag = hashlib.md5((str(timestamp) + str(size)).encode("ascii")).hexdigest() return etag, max_age def _check_cache_control_headers(self, resp, etag, max_age, timestamp=1234567890.0): - eq_(resp.headers['ETag'], etag) + assert resp.headers["ETag"] == etag if timestamp is None: - assert 'Last-modified' not in resp.headers + assert "Last-modified" not in resp.headers else: - eq_(resp.headers['Last-modified'], format_httpdate(timestamp)) - eq_(resp.headers['Cache-control'], 'public, max-age=%d, s-maxage=%d' % (max_age, max_age)) + assert resp.headers["Last-modified"] == format_httpdate(timestamp) + assert resp.headers["Cache-control"] == "public, max-age=%d, s-maxage=%d" % ( + max_age, + max_age, + ) - def test_get_cached_tile(self): - etag, max_age = self._update_timestamp() - resp = self.app.get('/kml/wms_cache/1/0/1.jpeg') + def test_get_cached_tile(self, app, base_config, cache_dir, fixture_cache_data): + etag, max_age = self._update_timestamp(base_config, cache_dir) + resp = app.get("/kml/wms_cache/1/0/1.jpeg") self._check_cache_control_headers(resp, etag, max_age) self._check_tile_resp(resp) - def test_if_none_match(self): - etag, max_age = self._update_timestamp() - resp = self.app.get('/kml/wms_cache/1/0/1.jpeg', - headers={'If-None-Match': etag}) - eq_(resp.status, '304 Not Modified') + def test_if_none_match(self, app, base_config, cache_dir, fixture_cache_data): + etag, max_age = self._update_timestamp(base_config, cache_dir) + resp = app.get("/kml/wms_cache/1/0/1.jpeg", headers={"If-None-Match": etag}) + assert resp.status == "304 Not Modified" self._check_cache_control_headers(resp, etag, max_age) - resp = self.app.get('/kml/wms_cache/1/0/1.jpeg', - headers={'If-None-Match': etag + 'foo'}) + resp = app.get( + "/kml/wms_cache/1/0/1.jpeg", headers={"If-None-Match": etag + "foo"} + ) self._check_cache_control_headers(resp, etag, max_age) - eq_(resp.status, '200 OK') + assert resp.status == "200 OK" self._check_tile_resp(resp) - def test_get_kml(self): - resp = self.app.get('/kml/wms_cache/0/0/0.kml') + def test_get_kml(self, app, base_config): + resp = app.get("/kml/wms_cache/0/0/0.kml") xml = resp.lxml - assert validate_with_xsd(xml, 'kml/2.2.0/ogckml22.xsd') + assert validate_with_xsd(xml, "kml/2.2.0/ogckml22.xsd") assert bbox_equals( - self._bbox(xml.xpath('/kml:kml/kml:Document', namespaces=ns)[0]), - (-180, -90, 180, 90) + self._bbox(xml.xpath("/kml:kml/kml:Document", namespaces=ns)[0]), + (-180, -90, 180, 90), ) assert bbox_equals( - self._bbox(xml.xpath('/kml:kml/kml:Document/kml:GroundOverlay', namespaces=ns)[0]), - (-180, 0, 0, 90) - ) - eq_(xml.xpath('/kml:kml/kml:Document/kml:GroundOverlay/kml:Icon/kml:href/text()', - namespaces=ns), - ['http://localhost/kml/wms_cache/EPSG900913/1/0/1.jpeg', - 'http://localhost/kml/wms_cache/EPSG900913/1/1/1.jpeg', - 'http://localhost/kml/wms_cache/EPSG900913/1/0/0.jpeg', - 'http://localhost/kml/wms_cache/EPSG900913/1/1/0.jpeg'] - ) - eq_(xml.xpath('/kml:kml/kml:Document/kml:NetworkLink/kml:Link/kml:href/text()', - namespaces=ns), - ['http://localhost/kml/wms_cache/EPSG900913/1/0/1.kml', - 'http://localhost/kml/wms_cache/EPSG900913/1/1/1.kml', - 'http://localhost/kml/wms_cache/EPSG900913/1/0/0.kml', - 'http://localhost/kml/wms_cache/EPSG900913/1/1/0.kml'] - ) + self._bbox( + xml.xpath("/kml:kml/kml:Document/kml:GroundOverlay", namespaces=ns)[0] + ), + (-180, 0, 0, 90), + ) + assert xml.xpath( + "/kml:kml/kml:Document/kml:GroundOverlay/kml:Icon/kml:href/text()", + namespaces=ns, + ) == [ + "http://localhost/kml/wms_cache/EPSG900913/1/0/1.jpeg", + "http://localhost/kml/wms_cache/EPSG900913/1/1/1.jpeg", + "http://localhost/kml/wms_cache/EPSG900913/1/0/0.jpeg", + "http://localhost/kml/wms_cache/EPSG900913/1/1/0.jpeg", + ] + assert xml.xpath( + "/kml:kml/kml:Document/kml:NetworkLink/kml:Link/kml:href/text()", + namespaces=ns, + ) == [ + "http://localhost/kml/wms_cache/EPSG900913/1/0/1.kml", + "http://localhost/kml/wms_cache/EPSG900913/1/1/1.kml", + "http://localhost/kml/wms_cache/EPSG900913/1/0/0.kml", + "http://localhost/kml/wms_cache/EPSG900913/1/1/0.kml", + ] etag = hashlib.md5(resp.body).hexdigest() - max_age = base_config().tiles.expires_hours * 60 * 60 + max_age = base_config.tiles.expires_hours * 60 * 60 self._check_cache_control_headers(resp, etag, max_age, None) - resp = self.app.get('/kml/wms_cache/0/0/0.kml', - headers={'If-None-Match': etag}) - eq_(resp.status, '304 Not Modified') + resp = app.get("/kml/wms_cache/0/0/0.kml", headers={"If-None-Match": etag}) + assert resp.status == "304 Not Modified" - def test_get_kml_init(self): - resp = self.app.get('/kml/wms_cache') + def test_get_kml_init(self, app): + resp = app.get("/kml/wms_cache") xml = resp.lxml - assert validate_with_xsd(xml, 'kml/2.2.0/ogckml22.xsd') - eq_(xml.xpath('/kml:kml/kml:Document/kml:GroundOverlay/kml:Icon/kml:href/text()', - namespaces=ns), - ['http://localhost/kml/wms_cache/EPSG900913/1/0/1.jpeg', - 'http://localhost/kml/wms_cache/EPSG900913/1/1/1.jpeg', - 'http://localhost/kml/wms_cache/EPSG900913/1/0/0.jpeg', - 'http://localhost/kml/wms_cache/EPSG900913/1/1/0.jpeg'] - ) - eq_(xml.xpath('/kml:kml/kml:Document/kml:NetworkLink/kml:Link/kml:href/text()', - namespaces=ns), - ['http://localhost/kml/wms_cache/EPSG900913/1/0/1.kml', - 'http://localhost/kml/wms_cache/EPSG900913/1/1/1.kml', - 'http://localhost/kml/wms_cache/EPSG900913/1/0/0.kml', - 'http://localhost/kml/wms_cache/EPSG900913/1/1/0.kml'] - ) + assert validate_with_xsd(xml, "kml/2.2.0/ogckml22.xsd") + assert xml.xpath( + "/kml:kml/kml:Document/kml:GroundOverlay/kml:Icon/kml:href/text()", + namespaces=ns, + ) == [ + "http://localhost/kml/wms_cache/EPSG900913/1/0/1.jpeg", + "http://localhost/kml/wms_cache/EPSG900913/1/1/1.jpeg", + "http://localhost/kml/wms_cache/EPSG900913/1/0/0.jpeg", + "http://localhost/kml/wms_cache/EPSG900913/1/1/0.jpeg", + ] + assert xml.xpath( + "/kml:kml/kml:Document/kml:NetworkLink/kml:Link/kml:href/text()", + namespaces=ns, + ) == [ + "http://localhost/kml/wms_cache/EPSG900913/1/0/1.kml", + "http://localhost/kml/wms_cache/EPSG900913/1/1/1.kml", + "http://localhost/kml/wms_cache/EPSG900913/1/0/0.kml", + "http://localhost/kml/wms_cache/EPSG900913/1/1/0.kml", + ] - def test_get_kml_nw(self): - resp = self.app.get('/kml/wms_cache_nw/1/0/0.kml') + def test_get_kml_nw(self, app): + resp = app.get("/kml/wms_cache_nw/1/0/0.kml") xml = resp.lxml - assert validate_with_xsd(xml, 'kml/2.2.0/ogckml22.xsd') + assert validate_with_xsd(xml, "kml/2.2.0/ogckml22.xsd") assert bbox_equals( - self._bbox(xml.xpath('/kml:kml/kml:Document', namespaces=ns)[0]), - (-180, -90, 0, 0) + self._bbox(xml.xpath("/kml:kml/kml:Document", namespaces=ns)[0]), + (-180, -90, 0, 0), ) assert bbox_equals( - self._bbox(xml.xpath('/kml:kml/kml:Document/kml:GroundOverlay', namespaces=ns)[0]), - (-180, -66.51326, -90, 0) - ) + self._bbox( + xml.xpath("/kml:kml/kml:Document/kml:GroundOverlay", namespaces=ns)[0] + ), + (-180, -66.51326, -90, 0), + ) + + assert xml.xpath( + "/kml:kml/kml:Document/kml:GroundOverlay/kml:Icon/kml:href/text()", + namespaces=ns, + ) == [ + "http://localhost/kml/wms_cache_nw/EPSG900913/2/0/1.jpeg", + "http://localhost/kml/wms_cache_nw/EPSG900913/2/1/1.jpeg", + "http://localhost/kml/wms_cache_nw/EPSG900913/2/0/0.jpeg", + "http://localhost/kml/wms_cache_nw/EPSG900913/2/1/0.jpeg", + ] + assert xml.xpath( + "/kml:kml/kml:Document/kml:NetworkLink/kml:Link/kml:href/text()", + namespaces=ns, + ) == [ + "http://localhost/kml/wms_cache_nw/EPSG900913/2/0/1.kml", + "http://localhost/kml/wms_cache_nw/EPSG900913/2/1/1.kml", + "http://localhost/kml/wms_cache_nw/EPSG900913/2/0/0.kml", + "http://localhost/kml/wms_cache_nw/EPSG900913/2/1/0.kml", + ] - eq_(xml.xpath('/kml:kml/kml:Document/kml:GroundOverlay/kml:Icon/kml:href/text()', - namespaces=ns), - ['http://localhost/kml/wms_cache_nw/EPSG900913/2/0/1.jpeg', - 'http://localhost/kml/wms_cache_nw/EPSG900913/2/1/1.jpeg', - 'http://localhost/kml/wms_cache_nw/EPSG900913/2/0/0.jpeg', - 'http://localhost/kml/wms_cache_nw/EPSG900913/2/1/0.jpeg'] - ) - eq_(xml.xpath('/kml:kml/kml:Document/kml:NetworkLink/kml:Link/kml:href/text()', - namespaces=ns), - ['http://localhost/kml/wms_cache_nw/EPSG900913/2/0/1.kml', - 'http://localhost/kml/wms_cache_nw/EPSG900913/2/1/1.kml', - 'http://localhost/kml/wms_cache_nw/EPSG900913/2/0/0.kml', - 'http://localhost/kml/wms_cache_nw/EPSG900913/2/1/0.kml'] - ) - - def test_get_kml2(self): - resp = self.app.get('/kml/wms_cache/1/0/1.kml') + def test_get_kml2(self, app): + resp = app.get("/kml/wms_cache/1/0/1.kml") xml = resp.lxml - assert validate_with_xsd(xml, 'kml/2.2.0/ogckml22.xsd') + assert validate_with_xsd(xml, "kml/2.2.0/ogckml22.xsd") - def test_get_kml_multi_layer(self): - resp = self.app.get('/kml/wms_cache_multi/1/0/0.kml') + def test_get_kml_multi_layer(self, app): + resp = app.get("/kml/wms_cache_multi/1/0/0.kml") xml = resp.lxml - assert validate_with_xsd(xml, 'kml/2.2.0/ogckml22.xsd') - eq_(xml.xpath('/kml:kml/kml:Document/kml:GroundOverlay/kml:Icon/kml:href/text()', - namespaces=ns), - ['http://localhost/kml/wms_cache_multi/EPSG4326/2/0/1.jpeg', - 'http://localhost/kml/wms_cache_multi/EPSG4326/2/1/1.jpeg', - 'http://localhost/kml/wms_cache_multi/EPSG4326/2/0/0.jpeg', - 'http://localhost/kml/wms_cache_multi/EPSG4326/2/1/0.jpeg'] - ) - eq_(xml.xpath('/kml:kml/kml:Document/kml:NetworkLink/kml:Link/kml:href/text()', - namespaces=ns), - ['http://localhost/kml/wms_cache_multi/EPSG4326/2/0/1.kml', - 'http://localhost/kml/wms_cache_multi/EPSG4326/2/1/1.kml', - 'http://localhost/kml/wms_cache_multi/EPSG4326/2/0/0.kml', - 'http://localhost/kml/wms_cache_multi/EPSG4326/2/1/0.kml'] - ) - - def test_get_tile(self): - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg' - '&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=' - '&VERSION=1.1.1&BBOX=-20037508.3428,-20037508.3428,0.0,0.0' - '&WIDTH=256'}, - {'body': img.read(), 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - resp = self.app.get('/kml/wms_cache/1/0/0.jpeg') - eq_(resp.content_type, 'image/jpeg') - self.created_tiles.append('wms_cache_EPSG900913/01/000/000/000/000/000/000.jpeg') + assert validate_with_xsd(xml, "kml/2.2.0/ogckml22.xsd") + assert xml.xpath( + "/kml:kml/kml:Document/kml:GroundOverlay/kml:Icon/kml:href/text()", + namespaces=ns, + ) == [ + "http://localhost/kml/wms_cache_multi/EPSG4326/2/0/1.jpeg", + "http://localhost/kml/wms_cache_multi/EPSG4326/2/1/1.jpeg", + "http://localhost/kml/wms_cache_multi/EPSG4326/2/0/0.jpeg", + "http://localhost/kml/wms_cache_multi/EPSG4326/2/1/0.jpeg", + ] + assert xml.xpath( + "/kml:kml/kml:Document/kml:NetworkLink/kml:Link/kml:href/text()", + namespaces=ns, + ) == [ + "http://localhost/kml/wms_cache_multi/EPSG4326/2/0/1.kml", + "http://localhost/kml/wms_cache_multi/EPSG4326/2/1/1.kml", + "http://localhost/kml/wms_cache_multi/EPSG4326/2/0/0.kml", + "http://localhost/kml/wms_cache_multi/EPSG4326/2/1/0.kml", + ] + + def test_get_tile(self, app, cache_dir): + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg" + "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=" + "&VERSION=1.1.1&BBOX=-20037508.3428,-20037508.3428,0.0,0.0" + "&WIDTH=256" + }, + {"body": img.read(), "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + resp = app.get("/kml/wms_cache/1/0/0.jpeg") + assert resp.content_type == "image/jpeg" + assert cache_dir.join( + "wms_cache_EPSG900913/01/000/000/000/000/000/000.jpeg" + ).check() def _bbox(self, elem): - elems = elem.xpath('kml:Region/kml:LatLonAltBox', namespaces=ns)[0] + elems = elem.xpath("kml:Region/kml:LatLonAltBox", namespaces=ns)[0] n, s, e, w = [float(elem.text) for elem in elems.getchildren()] return w, s, e, n - diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_layergroups.py mapproxy-1.12.0/mapproxy/test/system/test_layergroups.py --- mapproxy-1.11.0/mapproxy/test/system/test_layergroups.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_layergroups.py 2019-08-30 07:34:08.000000000 +0000 @@ -1,12 +1,12 @@ # This file is part of the MapProxy project. # Copyright (C) 2010 Omniscale -# +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,129 +15,146 @@ from __future__ import division -from mapproxy.test.system import module_setup, module_teardown, SystemTest -from mapproxy.test.system.test_wms import is_111_capa, is_110_capa, is_100_capa, is_130_capa, ns130 +import pytest -from nose.tools import eq_ +from mapproxy.test.system import SysTest +from mapproxy.test.system.test_wms import ( + is_111_capa, + is_110_capa, + is_100_capa, + is_130_capa, + ns130, +) -test_config = {} -test_config_with_root = {} -def setup_module(): - module_setup(test_config, 'layergroups.yaml') - module_setup(test_config_with_root, 'layergroups_root.yaml') +TESTSERVER_ADDRESS = "localhost", 42423 -def teardown_module(): - module_teardown(test_config) - module_teardown(test_config_with_root) -TESTSERVER_ADDRESS = 'localhost', 42423 +class TestWMSWithRoot(SysTest): -class TestWMSWithRoot(SystemTest): - config = test_config_with_root - def setup(self): - SystemTest.setup(self) + @pytest.fixture(scope="class") + def config_file(self): + return "layergroups_root.yaml" def _check_layernames(self, xml): - eq_(xml.xpath('//Capability/Layer/Title/text()'), - ['Root Layer']) - eq_(xml.xpath('//Capability/Layer/Name/text()'), - ['root']) - eq_(xml.xpath('//Capability/Layer/Layer/Name/text()'), - ['layer1', 'layer2']) - eq_(xml.xpath('//Capability/Layer/Layer[1]/Layer/Name/text()'), - ['layer1a', 'layer1b']) - - def _check_layernames_with_namespace(self, xml, namespaces=None): - eq_(xml.xpath('//wms:Capability/wms:Layer/wms:Title/text()', namespaces=namespaces), - ['Root Layer']) - eq_(xml.xpath('//wms:Capability/wms:Layer/wms:Name/text()', namespaces=namespaces), - ['root']) - eq_(xml.xpath('//wms:Capability/wms:Layer/wms:Layer/wms:Name/text()', namespaces=namespaces), - ['layer1', 'layer2']) - eq_(xml.xpath('//wms:Capability/wms:Layer/wms:Layer[1]/wms:Layer/wms:Name/text()', namespaces=namespaces), - ['layer1a', 'layer1b']) + assert xml.xpath("//Capability/Layer/Title/text()") == ["Root Layer"] + assert xml.xpath("//Capability/Layer/Name/text()") == ["root"] + assert xml.xpath("//Capability/Layer/Layer/Name/text()") == ["layer1", "layer2"] + assert xml.xpath("//Capability/Layer/Layer[1]/Layer/Name/text()") == [ + "layer1a", + "layer1b", + ] + def _check_layernames_with_namespace(self, xml, namespaces=None): + assert xml.xpath( + "//wms:Capability/wms:Layer/wms:Title/text()", namespaces=namespaces + ) == ["Root Layer"] + assert xml.xpath( + "//wms:Capability/wms:Layer/wms:Name/text()", namespaces=namespaces + ) == ["root"] + assert xml.xpath( + "//wms:Capability/wms:Layer/wms:Layer/wms:Name/text()", + namespaces=namespaces, + ) == ["layer1", "layer2"] + assert xml.xpath( + "//wms:Capability/wms:Layer/wms:Layer[1]/wms:Layer/wms:Name/text()", + namespaces=namespaces, + ) == ["layer1a", "layer1b"] - def test_100_capa(self): - resp = self.app.get("/service?request=GetCapabilities&service=WMS&wmtver=1.0.0") + def test_100_capa(self, app): + resp = app.get("/service?request=GetCapabilities&service=WMS&wmtver=1.0.0") xml = resp.lxml assert is_100_capa(xml) self._check_layernames(xml) - - def test_110_capa(self): - resp = self.app.get("/service?request=GetCapabilities&service=WMS&version=1.1.0") + + def test_110_capa(self, app): + resp = app.get("/service?request=GetCapabilities&service=WMS&version=1.1.0") xml = resp.lxml assert is_110_capa(xml) self._check_layernames(xml) - def test_111_capa(self): - resp = self.app.get("/service?request=GetCapabilities&service=WMS&version=1.1.1") + def test_111_capa(self, app): + resp = app.get("/service?request=GetCapabilities&service=WMS&version=1.1.1") xml = resp.lxml assert is_111_capa(xml) self._check_layernames(xml) - def test_130_capa(self): - resp = self.app.get("/service?request=GetCapabilities&service=WMS&version=1.3.0") + def test_130_capa(self, app): + resp = app.get("/service?request=GetCapabilities&service=WMS&version=1.3.0") xml = resp.lxml assert is_130_capa(xml) self._check_layernames_with_namespace(xml, ns130) -class TestWMSWithoutRoot(SystemTest): - config = test_config - def setup(self): - SystemTest.setup(self) +class TestWMSWithoutRoot(SysTest): + + @pytest.fixture(scope="class") + def config_file(self): + return "layergroups.yaml" def _check_layernames(self, xml): - eq_(xml.xpath('//Capability/Layer/Title/text()'), - ['My WMS']) - eq_(xml.xpath('//Capability/Layer/Name/text()'), - []) - eq_(xml.xpath('//Capability/Layer/Layer/Name/text()'), - ['layer1', 'layer2']) - eq_(xml.xpath('//Capability/Layer/Layer[1]/Layer/Name/text()'), - ['layer1a', 'layer1b']) - eq_(xml.xpath('//Capability/Layer/Layer[2]/Layer/Name/text()'), - ['layer2a', 'layer2b']) - eq_(xml.xpath('//Capability/Layer/Layer[2]/Layer/Layer[1]/Name/text()'), - ['layer2b1']) - - def _check_layernames_with_namespace(self, xml, namespaces=None): - eq_(xml.xpath('//wms:Capability/wms:Layer/wms:Title/text()', namespaces=namespaces), - ['My WMS']) - eq_(xml.xpath('//wms:Capability/wms:Layer/wms:Name/text()', namespaces=namespaces), - []) - eq_(xml.xpath('//wms:Capability/wms:Layer/wms:Layer/wms:Name/text()', namespaces=namespaces), - ['layer1', 'layer2']) - eq_(xml.xpath('//wms:Capability/wms:Layer/wms:Layer[1]/wms:Layer/wms:Name/text()', namespaces=namespaces), - ['layer1a', 'layer1b']) - eq_(xml.xpath('//wms:Capability/wms:Layer/wms:Layer[2]/wms:Layer/wms:Name/text()', namespaces=namespaces), - ['layer2a', 'layer2b']) - eq_(xml.xpath('//wms:Capability/wms:Layer/wms:Layer[2]/wms:Layer/wms:Layer[1]/wms:Name/text()', namespaces=namespaces), - ['layer2b1']) + assert xml.xpath("//Capability/Layer/Title/text()") == ["My WMS"] + assert xml.xpath("//Capability/Layer/Name/text()") == [] + assert xml.xpath("//Capability/Layer/Layer/Name/text()") == ["layer1", "layer2"] + assert xml.xpath("//Capability/Layer/Layer[1]/Layer/Name/text()") == [ + "layer1a", + "layer1b", + ] + assert xml.xpath("//Capability/Layer/Layer[2]/Layer/Name/text()") == [ + "layer2a", + "layer2b", + ] + assert xml.xpath("//Capability/Layer/Layer[2]/Layer/Layer[1]/Name/text()") == [ + "layer2b1" + ] + def _check_layernames_with_namespace(self, xml, namespaces=None): + assert xml.xpath( + "//wms:Capability/wms:Layer/wms:Title/text()", namespaces=namespaces + ) == ["My WMS"] + assert ( + xml.xpath( + "//wms:Capability/wms:Layer/wms:Name/text()", namespaces=namespaces + ) + == [] + ) + assert xml.xpath( + "//wms:Capability/wms:Layer/wms:Layer/wms:Name/text()", + namespaces=namespaces, + ), ["layer1" == "layer2"] + assert xml.xpath( + "//wms:Capability/wms:Layer/wms:Layer[1]/wms:Layer/wms:Name/text()", + namespaces=namespaces, + ) == ["layer1a", "layer1b"] + assert xml.xpath( + "//wms:Capability/wms:Layer/wms:Layer[2]/wms:Layer/wms:Name/text()", + namespaces=namespaces, + ) == ["layer2a", "layer2b"] + assert xml.xpath( + "//wms:Capability/wms:Layer/wms:Layer[2]/wms:Layer/wms:Layer[1]/wms:Name/text()", + namespaces=namespaces, + ) == ["layer2b1"] - def test_100_capa(self): - resp = self.app.get("/service?request=GetCapabilities&service=WMS&wmtver=1.0.0") + def test_100_capa(self, app): + resp = app.get("/service?request=GetCapabilities&service=WMS&wmtver=1.0.0") xml = resp.lxml assert is_100_capa(xml) self._check_layernames(xml) - - def test_110_capa(self): - resp = self.app.get("/service?request=GetCapabilities&service=WMS&version=1.1.0") + + def test_110_capa(self, app): + resp = app.get("/service?request=GetCapabilities&service=WMS&version=1.1.0") xml = resp.lxml assert is_110_capa(xml) self._check_layernames(xml) - def test_111_capa(self): - resp = self.app.get("/service?request=GetCapabilities&service=WMS&version=1.1.1") + def test_111_capa(self, app): + resp = app.get("/service?request=GetCapabilities&service=WMS&version=1.1.1") xml = resp.lxml assert is_111_capa(xml) self._check_layernames(xml) - def test_130_capa(self): - resp = self.app.get("/service?request=GetCapabilities&service=WMS&version=1.3.0") + def test_130_capa(self, app): + resp = app.get("/service?request=GetCapabilities&service=WMS&version=1.3.0") xml = resp.lxml assert is_130_capa(xml) - self._check_layernames_with_namespace(xml, ns130) \ No newline at end of file + self._check_layernames_with_namespace(xml, ns130) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_legendgraphic.py mapproxy-1.12.0/mapproxy/test/system/test_legendgraphic.py --- mapproxy-1.11.0/mapproxy/test/system/test_legendgraphic.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_legendgraphic.py 2019-08-30 07:34:08.000000000 +0000 @@ -14,193 +14,257 @@ # limitations under the License. from __future__ import division + from io import BytesIO -from mapproxy.compat.image import Image +import pytest +from mapproxy.compat.image import Image from mapproxy.request.wms import ( - WMS111MapRequest, WMS111CapabilitiesRequest, WMS130CapabilitiesRequest, - WMS111LegendGraphicRequest, WMS130LegendGraphicRequest + WMS111MapRequest, + WMS111CapabilitiesRequest, + WMS130CapabilitiesRequest, + WMS111LegendGraphicRequest, + WMS130LegendGraphicRequest, ) -from mapproxy.test.system import module_setup, module_teardown, make_base_config, SystemTest from mapproxy.test.image import is_png, tmp_image -from mapproxy.test.http import mock_httpd from mapproxy.test.helper import validate_with_dtd, validate_with_xsd -from mapproxy.test.system.test_wms import is_111_capa, eq_xpath_wms130, ns130 -from nose.tools import eq_ +from mapproxy.test.http import mock_httpd +from mapproxy.test.system import SysTest +from mapproxy.test.system.test_wms import is_111_capa, assert_xpath_wms130, ns130 -test_config = {} -def setup_module(): - module_setup(test_config, 'legendgraphic.yaml', with_cache_data=False) +@pytest.fixture(scope="module") +def config_file(): + return "legendgraphic.yaml" -def teardown_module(): - module_teardown(test_config) -base_config = make_base_config(test_config) +def is_130_capa(xml): + return validate_with_xsd(xml, xsd_name="sld/1.1.0/sld_capabilities.xsd") -def is_130_capa(xml): - return validate_with_xsd(xml, xsd_name='sld/1.1.0/sld_capabilities.xsd') +class TestWMSLegendgraphic(SysTest): + # we use a class scoped cache_dir as tests build up on each other + @pytest.fixture(scope="class") + def cache_dir(self, base_dir): + return base_dir.join("cache_data") -class TestWMS(SystemTest): - config = test_config def setup(self): - SystemTest.setup(self) - self.common_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1')) - self.common_lg_req_111 = WMS111LegendGraphicRequest(url='/service?', - param=dict(format='image/png', layer='wms_legend', sld_version='1.1.0')) - self.common_lg_req_130 = WMS130LegendGraphicRequest(url='/service?', - param=dict(format='image/png', layer='wms_legend', sld_version='1.1.0')) - - #test_00, test_01, test_02 need to run first in order to run the other tests properly - def test_00_get_legendgraphic_multiple_sources_111(self): - self.common_lg_req_111.params['layer'] = 'wms_mult_sources' - with tmp_image((256, 256), format='png') as img: + self.common_req = WMS111MapRequest( + url="/service?", param=dict(service="WMS", version="1.1.1") + ) + self.common_lg_req_111 = WMS111LegendGraphicRequest( + url="/service?", + param=dict(format="image/png", layer="wms_legend", sld_version="1.1.0"), + ) + self.common_lg_req_130 = WMS130LegendGraphicRequest( + url="/service?", + param=dict(format="image/png", layer="wms_legend", sld_version="1.1.0"), + ) + + # test_00, test_01, test_02 need to run first in order to run the other tests properly + def test_00_get_legendgraphic_multiple_sources_111(self, app): + self.common_lg_req_111.params["layer"] = "wms_mult_sources" + with tmp_image((256, 256), format="png") as img: img_data = img.read() - expected_req1 = ({'path': r'/service?LAYER=foo&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetLegendGraphic&' - '&VERSION=1.1.1&SLD_VERSION=1.1.0'}, - {'body': img_data, 'headers': {'content-type': 'image/png'}}) - expected_req2 = ({'path': r'/service?LAYER=bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetLegendGraphic&' - '&VERSION=1.1.1&SLD_VERSION=1.1.0'}, - {'body': img_data, 'headers': {'content-type': 'image/png'}}) - expected_req3 = ({'path': r'/service?LAYER=spam&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetLegendGraphic&' - '&VERSION=1.1.1&SLD_VERSION=1.1.0'}, - {'body': img_data, 'headers': {'content-type': 'image/png'}}) - with mock_httpd(('localhost', 42423), [expected_req1, expected_req2, expected_req3]): - resp = self.app.get(self.common_lg_req_111) - eq_(resp.content_type, 'image/png') + expected_req1 = ( + { + "path": r"/service?LAYER=foo&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetLegendGraphic&" + "&VERSION=1.1.1&SLD_VERSION=1.1.0" + }, + {"body": img_data, "headers": {"content-type": "image/png"}}, + ) + expected_req2 = ( + { + "path": r"/service?LAYER=bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetLegendGraphic&" + "&VERSION=1.1.1&SLD_VERSION=1.1.0" + }, + {"body": img_data, "headers": {"content-type": "image/png"}}, + ) + expected_req3 = ( + { + "path": r"/service?LAYER=spam&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetLegendGraphic&" + "&VERSION=1.1.1&SLD_VERSION=1.1.0" + }, + {"body": img_data, "headers": {"content-type": "image/png"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req1, expected_req2, expected_req3] + ): + resp = app.get(self.common_lg_req_111) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - assert Image.open(data).size == (256,768) + assert Image.open(data).size == (256, 768) - def test_01_get_legendgraphic_source_static_url(self): - self.common_lg_req_111.params['layer'] = 'wms_source_static_url' - with tmp_image((256, 256), format='png') as img: + def test_01_get_legendgraphic_source_static_url(self, app): + self.common_lg_req_111.params["layer"] = "wms_source_static_url" + with tmp_image((256, 256), format="png") as img: img_data = img.read() - expected_req1 = ({'path': r'/staticlegend_source.png'}, - {'body': img_data, 'headers': {'content-type': 'image/png'}}) - with mock_httpd(('localhost', 42423), [expected_req1]): - resp = self.app.get(self.common_lg_req_111) - eq_(resp.content_type, 'image/png') + expected_req1 = ( + {"path": r"/staticlegend_source.png"}, + {"body": img_data, "headers": {"content-type": "image/png"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req1]): + resp = app.get(self.common_lg_req_111) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - assert Image.open(data).size == (256,256) + assert Image.open(data).size == (256, 256) - def test_02_get_legendgraphic_layer_static_url(self): - self.common_lg_req_111.params['layer'] = 'wms_layer_static_url' - with tmp_image((256, 256), format='png') as img: + def test_02_get_legendgraphic_layer_static_url(self, app): + self.common_lg_req_111.params["layer"] = "wms_layer_static_url" + with tmp_image((256, 256), format="png") as img: img_data = img.read() - expected_req1 = ({'path': r'/staticlegend_layer.png'}, - {'body': img_data, 'headers': {'content-type': 'image/png'}}) - with mock_httpd(('localhost', 42423), [expected_req1]): - resp = self.app.get(self.common_lg_req_111) - eq_(resp.content_type, 'image/png') + expected_req1 = ( + {"path": r"/staticlegend_layer.png"}, + {"body": img_data, "headers": {"content-type": "image/png"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req1]): + resp = app.get(self.common_lg_req_111) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - assert Image.open(data).size == (256,256) + assert Image.open(data).size == (256, 256) - def test_capabilities_111(self): - req = WMS111CapabilitiesRequest(url='/service?').copy_with_request_params(self.common_req) - resp = self.app.get(req) - xml = resp.lxml - eq_(xml.xpath('//Request/GetLegendGraphic')[0].tag, 'GetLegendGraphic') - legend_sizes = (xml.xpath('//Layer/Style/LegendURL/@width'), - xml.xpath('//Layer/Style/LegendURL/@height')) - assert legend_sizes == (['256', '256', '256', '256'],['512', '768', '256', '256']) - layer_urls = xml.xpath('//Layer/Style/LegendURL/OnlineResource/@xlink:href', - namespaces=ns130) + def test_capabilities_111(self, app): + req = WMS111CapabilitiesRequest(url="/service?").copy_with_request_params( + self.common_req + ) + resp = app.get(req) + xml = resp.lxml + assert xml.xpath("//Request/GetLegendGraphic")[0].tag == "GetLegendGraphic" + legend_sizes = ( + xml.xpath("//Layer/Style/LegendURL/@width"), + xml.xpath("//Layer/Style/LegendURL/@height"), + ) + assert legend_sizes == ( + ["256", "256", "256", "256"], + ["512", "768", "256", "256"], + ) + layer_urls = xml.xpath( + "//Layer/Style/LegendURL/OnlineResource/@xlink:href", namespaces=ns130 + ) for layer_url in layer_urls: - assert layer_url.startswith('http://') - assert 'GetLegendGraphic' in layer_url + assert layer_url.startswith("http://") + assert "GetLegendGraphic" in layer_url assert is_111_capa(xml) - def test_capabilities_130(self): - req = WMS130CapabilitiesRequest(url='/service?').copy_with_request_params(self.common_req) - resp = self.app.get(req) - xml = resp.lxml - eq_(xml.xpath('//wms:Request/sld:GetLegendGraphic', namespaces=ns130)[0].tag, - '{%s}GetLegendGraphic'%(ns130['sld'])) - layer_urls = xml.xpath('//Layer/Style/LegendURL/OnlineResource/@xlink:href', - namespaces=ns130) + def test_capabilities_130(self, app): + req = WMS130CapabilitiesRequest(url="/service?").copy_with_request_params( + self.common_req + ) + resp = app.get(req) + xml = resp.lxml + assert xml.xpath("//wms:Request/sld:GetLegendGraphic", namespaces=ns130)[ + 0 + ].tag == "{%s}GetLegendGraphic" % (ns130["sld"]) + layer_urls = xml.xpath( + "//Layer/Style/LegendURL/OnlineResource/@xlink:href", namespaces=ns130 + ) for layer_url in layer_urls: - assert layer_url.startswith('http://') - assert 'GetLegendGraphic' in layer_url + assert layer_url.startswith("http://") + assert "GetLegendGraphic" in layer_url assert is_130_capa(xml) - def test_get_legendgraphic_111(self): - self.common_lg_req_111.params['scale'] = '5.0' - with tmp_image((256, 256), format='png') as img: + def test_get_legendgraphic_111(self, app): + self.common_lg_req_111.params["scale"] = "5.0" + with tmp_image((256, 256), format="png") as img: img_data = img.read() - expected_req1 = ({'path': r'/service?LAYER=foo&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetLegendGraphic&SCALE=5.0&' - '&VERSION=1.1.1&SLD_VERSION=1.1.0'}, - {'body': img_data, 'headers': {'content-type': 'image/png'}}) - expected_req2 = ({'path': r'/service?LAYER=bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetLegendGraphic&SCALE=5.0&' - '&VERSION=1.1.1&SLD_VERSION=1.1.0'}, - {'body': img_data, 'headers': {'content-type': 'image/png'}}) - with mock_httpd(('localhost', 42423), [expected_req1, expected_req2]): - resp = self.app.get(self.common_lg_req_111) - eq_(resp.content_type, 'image/png') + expected_req1 = ( + { + "path": r"/service?LAYER=foo&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetLegendGraphic&SCALE=5.0&" + "&VERSION=1.1.1&SLD_VERSION=1.1.0" + }, + {"body": img_data, "headers": {"content-type": "image/png"}}, + ) + expected_req2 = ( + { + "path": r"/service?LAYER=bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetLegendGraphic&SCALE=5.0&" + "&VERSION=1.1.1&SLD_VERSION=1.1.0" + }, + {"body": img_data, "headers": {"content-type": "image/png"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req1, expected_req2]): + resp = app.get(self.common_lg_req_111) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - assert Image.open(data).size == (256,512) + assert Image.open(data).size == (256, 512) + + def test_get_legendgraphic_no_legend_111(self, app): + self.common_lg_req_111.params["layer"] = "wms_no_legend" + resp = app.get(self.common_lg_req_111) + assert resp.content_type == "application/vnd.ogc.se_xml" + xml = resp.lxml + assert ( + "wms_no_legend has no legend graphic" + in xml.xpath("//ServiceException/text()")[0] + ) + assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd") + + def test_get_legendgraphic_missing_params_111(self, app): + req = ( + str(self.common_lg_req_111) + .replace("sld_version", "invalid") + .replace("format", "invalid") + ) + resp = app.get(req) + assert resp.content_type == "application/vnd.ogc.se_xml" + xml = resp.lxml + assert "missing parameters" in xml.xpath("//ServiceException/text()")[0] + assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd") - def test_get_legendgraphic_no_legend_111(self): - self.common_lg_req_111.params['layer'] = 'wms_no_legend' - resp = self.app.get(self.common_lg_req_111) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - xml = resp.lxml - assert 'wms_no_legend has no legend graphic' in xml.xpath('//ServiceException/text()')[0] - assert validate_with_dtd(xml, 'wms/1.1.1/exception_1_1_1.dtd') - - def test_get_legendgraphic_missing_params_111(self): - req = str(self.common_lg_req_111).replace('sld_version', 'invalid').replace('format', 'invalid') - resp = self.app.get(req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - xml = resp.lxml - assert 'missing parameters' in xml.xpath('//ServiceException/text()')[0] - assert validate_with_dtd(xml, 'wms/1.1.1/exception_1_1_1.dtd') - - def test_get_legendgraphic_invalid_sld_version_111(self): - req = str(self.common_lg_req_111).replace('sld_version=1.1.0', 'sld_version=1.0.0') - resp = self.app.get(req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - xml = resp.lxml - assert 'invalid sld_version' in xml.xpath('//ServiceException/text()')[0] - assert validate_with_dtd(xml, 'wms/1.1.1/exception_1_1_1.dtd') - - def test_get_legendgraphic_no_legend_130(self): - self.common_lg_req_130.params['layer'] = 'wms_no_legend' - resp = self.app.get(self.common_lg_req_130) - eq_(resp.content_type, 'text/xml') - xml = resp.lxml - eq_xpath_wms130(xml, '/ogc:ServiceExceptionReport/@version', '1.3.0') - eq_xpath_wms130(xml, '//ogc:ServiceException/text()', 'layer wms_no_legend has no legend graphic') - assert validate_with_xsd(xml, xsd_name='wms/1.3.0/exceptions_1_3_0.xsd') - - def test_get_legendgraphic_missing_params_130(self): - req = str(self.common_lg_req_130).replace('format', 'invalid') - resp = self.app.get(req) - eq_(resp.content_type, 'text/xml') - xml = resp.lxml - eq_xpath_wms130(xml, '/ogc:ServiceExceptionReport/@version', '1.3.0') - eq_xpath_wms130(xml, '//ogc:ServiceException/text()', "missing parameters ['format']") - assert validate_with_xsd(xml, xsd_name='wms/1.3.0/exceptions_1_3_0.xsd') - - def test_get_legendgraphic_invalid_sld_version_130(self): - req = str(self.common_lg_req_130).replace('sld_version=1.1.0', 'sld_version=1.0.0') - resp = self.app.get(req) - eq_(resp.content_type, 'text/xml') - xml = resp.lxml - eq_xpath_wms130(xml, '/ogc:ServiceExceptionReport/@version', '1.3.0') - eq_xpath_wms130(xml, '//ogc:ServiceException/text()', 'invalid sld_version 1.0.0') - assert validate_with_xsd(xml, xsd_name='wms/1.3.0/exceptions_1_3_0.xsd') + def test_get_legendgraphic_invalid_sld_version_111(self, app): + req = str(self.common_lg_req_111).replace( + "sld_version=1.1.0", "sld_version=1.0.0" + ) + resp = app.get(req) + assert resp.content_type == "application/vnd.ogc.se_xml" + xml = resp.lxml + assert "invalid sld_version" in xml.xpath("//ServiceException/text()")[0] + assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd") + def test_get_legendgraphic_no_legend_130(self, app): + self.common_lg_req_130.params["layer"] = "wms_no_legend" + resp = app.get(self.common_lg_req_130) + assert resp.content_type == "text/xml" + xml = resp.lxml + assert_xpath_wms130(xml, "/ogc:ServiceExceptionReport/@version", "1.3.0") + assert_xpath_wms130( + xml, + "//ogc:ServiceException/text()", + "layer wms_no_legend has no legend graphic", + ) + assert validate_with_xsd(xml, xsd_name="wms/1.3.0/exceptions_1_3_0.xsd") + + def test_get_legendgraphic_missing_params_130(self, app): + req = str(self.common_lg_req_130).replace("format", "invalid") + resp = app.get(req) + assert resp.content_type == "text/xml" + xml = resp.lxml + assert_xpath_wms130(xml, "/ogc:ServiceExceptionReport/@version", "1.3.0") + assert_xpath_wms130( + xml, "//ogc:ServiceException/text()", "missing parameters ['format']" + ) + assert validate_with_xsd(xml, xsd_name="wms/1.3.0/exceptions_1_3_0.xsd") + + def test_get_legendgraphic_invalid_sld_version_130(self, app): + req = str(self.common_lg_req_130).replace( + "sld_version=1.1.0", "sld_version=1.0.0" + ) + resp = app.get(req) + assert resp.content_type == "text/xml" + xml = resp.lxml + assert_xpath_wms130(xml, "/ogc:ServiceExceptionReport/@version", "1.3.0") + assert_xpath_wms130( + xml, "//ogc:ServiceException/text()", "invalid sld_version 1.0.0" + ) + assert validate_with_xsd(xml, xsd_name="wms/1.3.0/exceptions_1_3_0.xsd") diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_mapnik.py mapproxy-1.12.0/mapproxy/test/system/test_mapnik.py --- mapproxy-1.11.0/mapproxy/test/system/test_mapnik.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_mapnik.py 2019-08-30 07:34:08.000000000 +0000 @@ -14,19 +14,27 @@ # limitations under the License. from __future__ import division -import os -from mapproxy.test.system import module_setup, module_teardown, SystemTest +from io import BytesIO + +import pytest from mapproxy.compat.image import Image -from io import BytesIO +from mapproxy.test.system import SysTest -from nose.tools import eq_ -test_config = {} +has_mapnik = True +try: + import mapnik +except ImportError: + try: + import mapnik2 as mapnik + except ImportError: + has_mapnik = False -mapnik_xml = b""" +mapnik_xml = ( + b""" @@ -45,111 +53,111 @@ """.strip() +) -test_point_geojson = b""" +test_point_geojson = ( + b""" {"type": "Feature", "geometry": {"type": "Point", "coordinates": [-45, -45]}, "properties": {}} """.strip() +) -mapnik_transp_xml = b""" +mapnik_transp_xml = ( + b""" """.strip() +) -def setup_module(): - try: - import mapnik - mapnik - except ImportError: - try: - import mapnik2 as mapnik - mapnik - except ImportError: - from nose.plugins.skip import SkipTest - raise SkipTest('requires mapnik') - - module_setup(test_config, 'mapnik_source.yaml') - with open(os.path.join(test_config['base_dir'], 'test_point.geojson'), 'wb') as f: - f.write(test_point_geojson) - with open(os.path.join(test_config['base_dir'], 'mapnik.xml'), 'wb') as f: - f.write(mapnik_xml) - with open(os.path.join(test_config['base_dir'], 'mapnik-transparent.xml'), 'wb') as f: - f.write(mapnik_transp_xml) - - -def teardown_module(): - module_teardown(test_config) - -class TestMapnikSource(SystemTest): - config = test_config - - def test_get_map(self): - req = (r'/service?LAYERs=mapnik&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326' - '&VERSION=1.1.1&BBOX=-90,-90,0,0&styles=' - '&WIDTH=200&') +@pytest.fixture(scope="module") +def config_file(): + return "mapnik_source.yaml" + - resp = self.app.get(req) +@pytest.fixture(scope="module") +def additional_files(base_dir): + base_dir.join("test_point.geojson").write_binary(test_point_geojson) + base_dir.join("mapnik.xml").write_binary(mapnik_xml) + base_dir.join("mapnik-transparent.xml").write_binary(mapnik_transp_xml) + + +@pytest.mark.skipif(not has_mapnik, reason="requires mapnik") +class TestMapnikSource(SysTest): + + def test_get_map(self, app): + req = ( + r"/service?LAYERs=mapnik&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326" + "&VERSION=1.1.1&BBOX=-90,-90,0,0&styles=" + "&WIDTH=200&" + ) + + resp = app.get(req) data = BytesIO(resp.body) img = Image.open(data) colors = sorted(img.getcolors(), reverse=True) # map bg color + black marker assert 39700 < colors[0][0] < 39900, colors[0][0] - eq_(colors[0][1], (255, 0, 0, 255)) + assert colors[0][1] == (255, 0, 0, 255) assert 50 < colors[1][0] < 150, colors[1][0] - eq_(colors[1][1], (0, 0, 0, 255)) + assert colors[1][1] == (0, 0, 0, 255) - def test_get_map_hq(self): - req = (r'/service?LAYERs=mapnik_hq&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326' - '&VERSION=1.1.1&BBOX=-90,-90,0,0&styles=' - '&WIDTH=200&') + def test_get_map_hq(self, app): + req = ( + r"/service?LAYERs=mapnik_hq&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326" + "&VERSION=1.1.1&BBOX=-90,-90,0,0&styles=" + "&WIDTH=200&" + ) - resp = self.app.get(req) + resp = app.get(req) data = BytesIO(resp.body) img = Image.open(data) colors = sorted(img.getcolors(), reverse=True) # map bg color + black marker (like above, but marker is scaled up) assert 39500 < colors[0][0] < 39600, colors[0][0] - eq_(colors[0][1], (255, 0, 0, 255)) + assert colors[0][1] == (255, 0, 0, 255) assert 250 < colors[1][0] < 350, colors[1][0] - eq_(colors[1][1], (0, 0, 0, 255)) + assert colors[1][1] == (0, 0, 0, 255) - def test_get_map_outside_coverage(self): - req = (r'/service?LAYERs=mapnik&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326' - '&VERSION=1.1.1&BBOX=-175,-85,-172,-82&styles=' - '&WIDTH=200&&BGCOLOR=0x00ff00') + def test_get_map_outside_coverage(self, app): + req = ( + r"/service?LAYERs=mapnik&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326" + "&VERSION=1.1.1&BBOX=-175,-85,-172,-82&styles=" + "&WIDTH=200&&BGCOLOR=0x00ff00" + ) - resp = self.app.get(req) + resp = app.get(req) data = BytesIO(resp.body) img = Image.open(data) colors = sorted(img.getcolors(), reverse=True) # wms request bg color - eq_(colors[0], (40000, (0, 255, 0))) + assert colors[0] == (40000, (0, 255, 0)) - def test_get_map_unknown_file(self): - req = (r'/service?LAYERs=mapnik_unknown&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326' - '&VERSION=1.1.1&BBOX=-90,-90,0,0&styles=' - '&WIDTH=200&&BGCOLOR=0x00ff00') - - resp = self.app.get(req) - assert 'unknown.xml' in resp.body, resp.body - - def test_get_map_transparent(self): - req = (r'/service?LAYERs=mapnik_transparent&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326' - '&VERSION=1.1.1&BBOX=-90,-90,0,0&styles=' - '&WIDTH=200&transparent=True') + def test_get_map_unknown_file(self, app): + req = ( + r"/service?LAYERs=mapnik_unknown&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326" + "&VERSION=1.1.1&BBOX=-90,-90,0,0&styles=" + "&WIDTH=200&&BGCOLOR=0x00ff00" + ) + + resp = app.get(req) + assert "unknown.xml" in resp.body, resp.body + + def test_get_map_transparent(self, app): + req = ( + r"/service?LAYERs=mapnik_transparent&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326" + "&VERSION=1.1.1&BBOX=-90,-90,0,0&styles=" + "&WIDTH=200&transparent=True" + ) - resp = self.app.get(req) + resp = app.get(req) data = BytesIO(resp.body) img = Image.open(data) colors = sorted(img.getcolors(), reverse=True) - eq_(colors[0], (40000, (0, 0, 0, 0))) - - - + assert colors[0] == (40000, (0, 0, 0, 0)) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_mapserver.py mapproxy-1.12.0/mapproxy/test/system/test_mapserver.py --- mapproxy-1.11.0/mapproxy/test/system/test_mapserver.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_mapserver.py 2019-08-30 07:34:08.000000000 +0000 @@ -16,54 +16,67 @@ from __future__ import division import os +import sys import stat -import platform import shutil from io import BytesIO +import pytest + from mapproxy.request.wms import WMS111MapRequest from mapproxy.compat.image import Image from mapproxy.test.image import is_png -from mapproxy.test.system import prepare_env, create_app, module_teardown, SystemTest -from nose.tools import eq_ -from nose.plugins.skip import SkipTest +from mapproxy.test.system import SysTest + -test_config = {} +pytestmark = pytest.mark.skipif( + sys.platform == "win32", reason="CGI tests not ported for Windows" +) -def setup_module(): - if platform.system() == 'Windows': - raise SkipTest('CGI test only works on Unix (for now)') - prepare_env(test_config, 'mapserver.yaml') +@pytest.fixture(scope="module") +def config_file(): + return "mapserver.yaml" - shutil.copy(os.path.join(test_config['fixture_dir'], 'cgi.py'), - test_config['base_dir']) - os.chmod(os.path.join(test_config['base_dir'], 'cgi.py'), - stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR) +class TestMapServerCGI(SysTest): - os.mkdir(os.path.join(test_config['base_dir'], 'tmp')) + @pytest.fixture(scope="class") + def additional_files(self, base_dir): + shutil.copy( + os.path.join(os.path.dirname(__file__), "fixture", "cgi.py"), + base_dir.strpath, + ) - create_app(test_config) + os.chmod( + base_dir.join("cgi.py").strpath, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR + ) -def teardown_module(): - module_teardown(test_config) + base_dir.join("tmp").mkdir() -class TestMapServerCGI(SystemTest): - config = test_config def setup(self): - SystemTest.setup(self) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='-180,0,0,80', width='200', height='200', - layers='ms', srs='EPSG:4326', format='image/png', - styles='', request='GetMap')) - - def test_get_map(self): - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="-180,0,0,80", + width="200", + height="200", + layers="ms", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + ), + ) + + def test_get_map(self, app): + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) - img = img.convert('RGB') - eq_(img.getcolors(), [(200*200, (255, 0, 0))]) + img = img.convert("RGB") + assert img.getcolors() == [(200 * 200, (255, 0, 0))] diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_mixed_mode_format.py mapproxy-1.12.0/mapproxy/test/system/test_mixed_mode_format.py --- mapproxy-1.11.0/mapproxy/test/system/test_mixed_mode_format.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_mixed_mode_format.py 2019-08-30 07:34:08.000000000 +0000 @@ -14,127 +14,170 @@ # limitations under the License. from __future__ import division -import os + +from contextlib import contextmanager from io import BytesIO -from mapproxy.compat.image import ( - Image, - ImageDraw, - ImageColor, -) + +import pytest + +from mapproxy.compat.image import Image, ImageDraw, ImageColor from mapproxy.request.wms import WMS111MapRequest from mapproxy.request.wmts import WMTS100TileRequest from mapproxy.test.image import check_format, is_transparent from mapproxy.test.http import mock_httpd -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config -from nose.tools import eq_ -from contextlib import contextmanager +from mapproxy.test.system import SysTest -test_config = {} -base_config = make_base_config(test_config) -def setup_module(): - module_setup(test_config, 'mixed_mode.yaml', with_cache_data=True) +@pytest.fixture(scope="module") +def config_file(): + return "mixed_mode.yaml" -def teardown_module(): - module_teardown(test_config) -class TestWMS(SystemTest): - config = test_config +class TestWMS(SysTest): def setup(self): - SystemTest.setup(self) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='0,0,180,80', width='200', height='200', - layers='mixed_mode', srs='EPSG:4326', format='image/png', - styles='', request='GetMap', transparent='true')) - self.expected_base_path = '/service?SERVICE=WMS&REQUEST=GetMap&HEIGHT=256' \ - '&SRS=EPSG%3A900913&styles=&VERSION=1.1.1&WIDTH=512' \ - '&BBOX=-20037508.3428,0.0,20037508.3428,20037508.3428' - - def test_mixed_mode(self): - req_format = 'png' - transparent = 'True' + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="0,0,180,80", + width="200", + height="200", + layers="mixed_mode", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + transparent="true", + ), + ) + self.expected_base_path = "/service?SERVICE=WMS&REQUEST=GetMap&HEIGHT=256" "&SRS=EPSG%3A900913&styles=&VERSION=1.1.1&WIDTH=512" "&BBOX=-20037508.3428,0.0,20037508.3428,20037508.3428" + + def test_mixed_mode(self, app, cache_dir): + req_format = "png" + transparent = "True" with create_mixed_mode_img((512, 256)) as img: - expected_req = ({'path': self.expected_base_path + - '&layers=mixedsource' + - '&format=image%2F' + req_format + - '&transparent=' + transparent}, - {'body': img.read(), 'headers': {'content-type': 'image/'+req_format}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - self.common_map_req.params['format'] = 'image/'+req_format - resp = self.app.get(self.common_map_req) - self.created_tiles.append('mixed_cache_EPSG900913/01/000/000/000/000/000/001.mixed') - self.created_tiles.append('mixed_cache_EPSG900913/01/000/000/001/000/000/001.mixed') + expected_req = ( + { + "path": self.expected_base_path + + "&layers=mixedsource" + + "&format=image%2F" + + req_format + + "&transparent=" + + transparent + }, + { + "body": img.read(), + "headers": {"content-type": "image/" + req_format}, + }, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + self.common_map_req.params["format"] = "image/" + req_format + resp = app.get(self.common_map_req) - eq_(resp.content_type, 'image/'+req_format) + assert resp.content_type == "image/" + req_format check_format(BytesIO(resp.body), req_format) # GetMap Request is fully within the opaque tile assert not is_transparent(resp.body) - # check cache formats - cache_dir = base_config().cache.base_dir - check_format(open(os.path.join(cache_dir, self.created_tiles[0]), 'rb'), 'png') - check_format(open(os.path.join(cache_dir, self.created_tiles[1]), 'rb'), 'jpeg') + # check cache formats + for f, format in [ + ["mixed_cache_EPSG900913/01/000/000/000/000/000/001.mixed", "png"], + ["mixed_cache_EPSG900913/01/000/000/001/000/000/001.mixed", "jpeg"], + ]: + assert cache_dir.join(f).check() + check_format(cache_dir.join(f).read_binary(), format) -class TestTMS(SystemTest): - config = test_config + +class TestTMS(SysTest): def setup(self): - SystemTest.setup(self) - self.expected_base_path = '/service?SERVICE=WMS&REQUEST=GetMap&HEIGHT=256' \ - '&SRS=EPSG%3A900913&styles=&VERSION=1.1.1&WIDTH=512' \ - '&BBOX=-20037508.3428,-20037508.3428,20037508.3428,0.0' + self.expected_base_path = "/service?SERVICE=WMS&REQUEST=GetMap&HEIGHT=256" "&SRS=EPSG%3A900913&styles=&VERSION=1.1.1&WIDTH=512" "&BBOX=-20037508.3428,-20037508.3428,20037508.3428,0.0" - def test_mixed_mode(self): + def test_mixed_mode(self, app, cache_dir): with create_mixed_mode_img((512, 256)) as img: - expected_req = ({'path': self.expected_base_path + - '&layers=mixedsource' + - '&format=image%2Fpng' + - '&transparent=True'}, - {'body': img.read(), 'headers': {'content-type': 'image/png'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - resp = self.app.get('/tms/1.0.0/mixed_mode/0/0/0.png') - eq_(resp.content_type, 'image/png') + expected_req = ( + { + "path": self.expected_base_path + + "&layers=mixedsource" + + "&format=image%2Fpng" + + "&transparent=True" + }, + {"body": img.read(), "headers": {"content-type": "image/png"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + resp = app.get("/tms/1.0.0/mixed_mode/0/0/0.png") + assert resp.content_type == "image/png" assert is_transparent(resp.body) - resp = self.app.get('/tms/1.0.0/mixed_mode/0/1/0.png') + resp = app.get("/tms/1.0.0/mixed_mode/0/1/0.png") + + assert resp.content_type == "image/jpeg" + assert cache_dir.join( + "mixed_cache_EPSG900913/01/000/000/000/000/000/000.mixed" + ).check() + assert cache_dir.join( + "mixed_cache_EPSG900913/01/000/000/001/000/000/000.mixed" + ).check() - eq_(resp.content_type, 'image/jpeg') - self.created_tiles.append('mixed_cache_EPSG900913/01/000/000/000/000/000/000.mixed') - self.created_tiles.append('mixed_cache_EPSG900913/01/000/000/001/000/000/000.mixed') -class TestWMTS(SystemTest): - config = test_config +class TestWMTS(SysTest): def setup(self): - SystemTest.setup(self) - self.common_tile_req = WMTS100TileRequest(url='/service?', param=dict(service='WMTS', - version='1.0.0', tilerow='0', tilecol='0', tilematrix='01', tilematrixset='GLOBAL_MERCATOR', - layer='mixed_mode', format='image/png', style='', request='GetTile', transparent='True')) - self.expected_base_path = '/service?SERVICE=WMS&REQUEST=GetMap&HEIGHT=256' \ - '&SRS=EPSG%3A900913&styles=&VERSION=1.1.1&WIDTH=512' \ - '&BBOX=-20037508.3428,0.0,20037508.3428,20037508.3428' + self.common_tile_req = WMTS100TileRequest( + url="/service?", + param=dict( + service="WMTS", + version="1.0.0", + tilerow="0", + tilecol="0", + tilematrix="01", + tilematrixset="GLOBAL_MERCATOR", + layer="mixed_mode", + format="image/png", + style="", + request="GetTile", + transparent="True", + ), + ) + self.expected_base_path = "/service?SERVICE=WMS&REQUEST=GetMap&HEIGHT=256" "&SRS=EPSG%3A900913&styles=&VERSION=1.1.1&WIDTH=512" "&BBOX=-20037508.3428,0.0,20037508.3428,20037508.3428" - def test_mixed_mode(self): + def test_mixed_mode(self, app, cache_dir): with create_mixed_mode_img((512, 256)) as img: - expected_req = ({'path': self.expected_base_path + - '&layers=mixedsource' + - '&format=image%2Fpng' + - '&transparent=True'}, - {'body': img.read(), 'headers': {'content-type': 'image/png'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - resp = self.app.get(self.common_tile_req) - eq_(resp.content_type, 'image/png') + expected_req = ( + { + "path": self.expected_base_path + + "&layers=mixedsource" + + "&format=image%2Fpng" + + "&transparent=True" + }, + {"body": img.read(), "headers": {"content-type": "image/png"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + resp = app.get(self.common_tile_req) + assert resp.content_type == "image/png" assert is_transparent(resp.body) - self.created_tiles.append('mixed_cache_EPSG900913/01/000/000/000/000/000/001.mixed') - self.common_tile_req.params['tilecol'] = '1' - resp = self.app.get(self.common_tile_req) - eq_(resp.content_type, 'image/jpeg') - self.created_tiles.append('mixed_cache_EPSG900913/01/000/000/001/000/000/001.mixed') + self.common_tile_req.params["tilecol"] = "1" + resp = app.get(self.common_tile_req) + assert resp.content_type == "image/jpeg" + assert cache_dir.join( + "mixed_cache_EPSG900913/01/000/000/000/000/000/001.mixed" + ).check() + assert cache_dir.join( + "mixed_cache_EPSG900913/01/000/000/001/000/000/001.mixed" + ).check() + @contextmanager -def create_mixed_mode_img(size, format='png'): +def create_mixed_mode_img(size, format="png"): img = Image.new("RGBA", size) # draw a black rectangle into the image, rect_width = 3/4 img_width @@ -144,10 +187,9 @@ draw = ImageDraw.Draw(img) w, h = size red_color = ImageColor.getrgb("red") - draw.rectangle((w/4, 0, w, h), fill=red_color) + draw.rectangle((w / 4, 0, w, h), fill=red_color) data = BytesIO() img.save(data, format) data.seek(0) yield data - diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_multiapp.py mapproxy-1.12.0/mapproxy/test/system/test_multiapp.py --- mapproxy-1.11.0/mapproxy/test/system/test_multiapp.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_multiapp.py 2019-08-30 07:34:08.000000000 +0000 @@ -14,90 +14,79 @@ # limitations under the License. from __future__ import division + import io import os -import tempfile import shutil -from webtest import TestApp -from mapproxy.multiapp import app_factory - -def module_setup(test_config, config_files): - fixture_dir = os.path.join(os.path.dirname(__file__), 'fixture') - test_config['base_dir'] = tempfile.mkdtemp() - test_config['config_files'] = [] +import pytest - for config_file in config_files: - config_file_src = os.path.join(fixture_dir, config_file) - config_file_dst = os.path.join(test_config['base_dir'], config_file) - shutil.copy(config_file_src, config_file_dst) - test_config['config_files'].append(config_file_dst) +from mapproxy.multiapp import make_wsgi_app - app = app_factory({}, config_dir=test_config['base_dir'], allow_listing=False) - test_config['multiapp'] = app - test_config['app'] = TestApp(app, use_unicode=False) +from mapproxy.test.system import SysTest, WSGITestApp -def module_teardown(test_config): - shutil.rmtree(test_config['base_dir']) - test_config.clear() +class TestMultiapp(SysTest): -test_config = {} + @pytest.fixture(scope="class") + def app(self, base_dir): + app = make_wsgi_app(base_dir.strpath, allow_listing=False) + return WSGITestApp(app, use_unicode=False) -def setup_module(): - module_setup(test_config, ['multiapp1.yaml', 'multiapp2.yaml']) + @pytest.fixture(scope="class") + def base_dir(self, tmpdir_factory): + dir = tmpdir_factory.mktemp("base_dir") -def teardown_module(): - module_teardown(test_config) + fixture_dir = os.path.join(os.path.dirname(__file__), "fixture") + shutil.copy(os.path.join(fixture_dir, "multiapp1.yaml"), dir.strpath) + shutil.copy(os.path.join(fixture_dir, "multiapp2.yaml"), dir.strpath) -class TestMultiapp(object): - def setup(self): - self.multiapp = test_config['multiapp'] - self.app = test_config['app'] + return dir - def test_index_without_list(self): - resp = self.app.get('/') - assert 'MapProxy' in resp - assert 'multiapp1' not in resp + def test_index_without_list(self, app): + resp = app.get("/") + assert "MapProxy" in resp + assert "multiapp1" not in resp - def test_index_with_list(self): + def test_index_with_list(self, app): try: - self.multiapp.list_apps = True - resp = self.app.get('/') - assert 'MapProxy' in resp - assert 'multiapp1' in resp + app.app.list_apps = True + resp = app.get("/") + assert "MapProxy" in resp + assert "multiapp1" in resp finally: - self.multiapp.list_apps = False + app.app.list_apps = False - def test_unknown_app(self): - self.app.get('/unknownapp', status=404) + def test_unknown_app(self, app): + app.get("/unknownapp", status=404) # assert status == 404 Not Found in app.get - def test_known_app(self): - resp = self.app.get('/multiapp1') - assert 'demo' in resp + def test_known_app(self, app): + resp = app.get("/multiapp1") + assert "demo" in resp + + def test_reloading(self, app, base_dir): + resp = app.get("/multiapp1") + assert "demo" in resp + app_config = base_dir.join("multiapp1.yaml").strpath - def test_reloading(self): - resp = self.app.get('/multiapp1') - assert 'demo' in resp - app_config = test_config['config_files'][0] + replace_text_in_file(app_config, " demo:", " #demo:", ts_delta=5) - replace_text_in_file(app_config, ' demo:', ' #demo:', ts_delta=5) + resp = app.get("/multiapp1") + assert "demo" not in resp - resp = self.app.get('/multiapp1') - assert 'demo' not in resp + replace_text_in_file(app_config, " #demo:", " demo:", ts_delta=10) - replace_text_in_file(app_config, ' #demo:', ' demo:', ts_delta=10) + resp = app.get("/multiapp1") + assert "demo" in resp - resp = self.app.get('/multiapp1') - assert 'demo' in resp def replace_text_in_file(filename, old, new, ts_delta=2): - text = io.open(filename, encoding='utf-8').read() + text = io.open(filename, encoding="utf-8").read() text = text.replace(old, new) - io.open(filename, 'w', encoding='utf-8').write(text) + io.open(filename, "w", encoding="utf-8").write(text) # file timestamps are not precise enough (1sec) # add larger delta to force reload m_time = os.path.getmtime(filename) - os.utime(filename, (m_time+ts_delta, m_time+ts_delta)) + os.utime(filename, (m_time + ts_delta, m_time + ts_delta)) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_multi_cache_layers.py mapproxy-1.12.0/mapproxy/test/system/test_multi_cache_layers.py --- mapproxy-1.11.0/mapproxy/test/system/test_multi_cache_layers.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_multi_cache_layers.py 2019-08-30 07:34:08.000000000 +0000 @@ -19,133 +19,149 @@ import functools from io import BytesIO -from mapproxy.request.wmts import ( - WMTS100TileRequest, WMTS100CapabilitiesRequest -) + +import pytest + +from mapproxy.request.wmts import WMTS100TileRequest, WMTS100CapabilitiesRequest from mapproxy.request.wms import WMS111CapabilitiesRequest from mapproxy.test.image import is_png, create_tmp_image from mapproxy.test.http import MockServ from mapproxy.test.helper import validate_with_xsd -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config -from nose.tools import eq_ - -test_config = {} -base_config = make_base_config(test_config) - +from mapproxy.test.system import SysTest -def setup_module(): - module_setup(test_config, 'multi_cache_layers.yaml', with_cache_data=True) +@pytest.fixture(scope="module") +def config_file(): + return "multi_cache_layers.yaml" -def teardown_module(): - module_teardown(test_config) ns_wmts = { - 'wmts': 'http://www.opengis.net/wmts/1.0', - 'ows': 'http://www.opengis.net/ows/1.1', - 'xlink': 'http://www.w3.org/1999/xlink' + "wmts": "http://www.opengis.net/wmts/1.0", + "ows": "http://www.opengis.net/ows/1.1", + "xlink": "http://www.w3.org/1999/xlink", } -def eq_xpath(xml, xpath, expected, namespaces=None): - eq_(xml.xpath(xpath, namespaces=namespaces)[0], expected) - -eq_xpath_wmts = functools.partial(eq_xpath, namespaces=ns_wmts) - - TEST_TILE = create_tmp_image((256, 256)) -class TestMultiCacheLayer(SystemTest): - config = test_config +class TestMultiCacheLayer(SysTest): def setup(self): - SystemTest.setup(self) self.common_cap_req = WMTS100CapabilitiesRequest( - url='/service?', - param=dict(service='WMTS', version='1.0.0', request='GetCapabilities')) + url="/service?", + param=dict(service="WMTS", version="1.0.0", request="GetCapabilities"), + ) self.common_tile_req = WMTS100TileRequest( - url='/service?', - param=dict(service='WMTS', version='1.0.0', tilerow='0', tilecol='0', tilematrix='01', - tilematrixset='GLOBAL_WEBMERCATOR', layer='multi_cache', format='image/png', style='', request='GetTile')) - - def test_tms_capabilities(self): - resp = self.app.get('/tms/1.0.0/') - assert 'http://localhost/tms/1.0.0/multi_cache/EPSG25832' in resp - assert 'http://localhost/tms/1.0.0/multi_cache/EPSG3857' in resp - assert 'http://localhost/tms/1.0.0/multi_cache/CRS84' in resp - assert 'http://localhost/tms/1.0.0/multi_cache/EPSG31467' in resp - assert 'http://localhost/tms/1.0.0/cache/EPSG25832' in resp + url="/service?", + param=dict( + service="WMTS", + version="1.0.0", + tilerow="0", + tilecol="0", + tilematrix="01", + tilematrixset="GLOBAL_WEBMERCATOR", + layer="multi_cache", + format="image/png", + style="", + request="GetTile", + ), + ) + + def test_tms_capabilities(self, app): + resp = app.get("/tms/1.0.0/") + assert "http://localhost/tms/1.0.0/multi_cache/EPSG25832" in resp + assert "http://localhost/tms/1.0.0/multi_cache/EPSG3857" in resp + assert "http://localhost/tms/1.0.0/multi_cache/CRS84" in resp + assert "http://localhost/tms/1.0.0/multi_cache/EPSG31467" in resp + assert "http://localhost/tms/1.0.0/cache/EPSG25832" in resp xml = resp.lxml - assert xml.xpath('count(//TileMap)') == 5 + assert xml.xpath("count(//TileMap)") == 5 - def test_wmts_capabilities(self): + def test_wmts_capabilities(self, app): req = str(self.common_cap_req) - resp = self.app.get(req) - eq_(resp.content_type, 'application/xml') + resp = app.get(req) + assert resp.content_type == "application/xml" xml = resp.lxml assert validate_with_xsd( - xml, xsd_name='wmts/1.0/wmtsGetCapabilities_response.xsd') - eq_(set(xml.xpath('//wmts:Layer/ows:Identifier/text()', - namespaces=ns_wmts)), set(['cache', 'multi_cache'])) - eq_(set(xml.xpath('//wmts:Contents/wmts:TileMatrixSet/ows:Identifier/text()', - namespaces=ns_wmts)), set(['gk3', 'GLOBAL_WEBMERCATOR', 'utm32', 'InspireCrs84Quad'])) - - def test_wms_capabilities(self): - req = WMS111CapabilitiesRequest(url='/service?') - resp = self.app.get(req) - eq_(resp.content_type, 'application/vnd.ogc.wms_xml') + xml, xsd_name="wmts/1.0/wmtsGetCapabilities_response.xsd" + ) + assert set( + xml.xpath("//wmts:Layer/ows:Identifier/text()", namespaces=ns_wmts) + ) == set(["cache", "multi_cache"]) + assert set( + xml.xpath( + "//wmts:Contents/wmts:TileMatrixSet/ows:Identifier/text()", + namespaces=ns_wmts, + ) + ) == set(["gk3", "GLOBAL_WEBMERCATOR", "utm32", "InspireCrs84Quad"]) + + def test_wms_capabilities(self, app): + req = WMS111CapabilitiesRequest(url="/service?") + resp = app.get(req) + assert resp.content_type == "application/vnd.ogc.wms_xml" xml = resp.lxml - eq_(xml.xpath('//GetMap//OnlineResource/@xlink:href', - namespaces=dict(xlink="http://www.w3.org/1999/xlink"))[0], - 'http://localhost/service?') - - layer_names = set(xml.xpath('//Layer/Layer/Name/text()')) - expected_names = set(['wms_only', 'cache']) - eq_(layer_names, expected_names) + assert ( + xml.xpath( + "//GetMap//OnlineResource/@xlink:href", + namespaces=dict(xlink="http://www.w3.org/1999/xlink"), + )[0] + == "http://localhost/service?" + ) + + layer_names = set(xml.xpath("//Layer/Layer/Name/text()")) + expected_names = set(["wms_only", "cache"]) + assert layer_names == expected_names - def test_get_tile_webmerc(self): + def test_get_tile_webmerc(self, app): serv = MockServ(42423, bbox_aware_query_comparator=True) serv.expects( - '/service?layers=foo,bar&width=256&version=1.1.1&bbox=-20037508.3428,0.0,0.0,20037508.3428&service=WMS&format=image%2Fpng&styles=&srs=EPSG%3A3857&request=GetMap&height=256').returns(TEST_TILE) + "/service?layers=foo,bar&width=256&version=1.1.1&bbox=-20037508.3428,0.0,0.0,20037508.3428&service=WMS&format=image%2Fpng&styles=&srs=EPSG%3A3857&request=GetMap&height=256" + ).returns(TEST_TILE) with serv: - resp = self.app.get(str(self.common_tile_req)) - eq_(resp.content_type, 'image/png') + resp = app.get(str(self.common_tile_req)) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - def test_get_tile_utm(self): + def test_get_tile_utm(self, app): serv = MockServ(42423, bbox_aware_query_comparator=True) serv.expects( - '/service?layers=foo,bar&width=256&version=1.1.1&bbox=-46133.17,5675047.40429,580038.965712,6301219.54&service=WMS&format=image%2Fpng&styles=&srs=EPSG%3A25832&request=GetMap&height=256').returns(TEST_TILE) - self.common_tile_req.params['tilematrixset'] = 'utm32' + "/service?layers=foo,bar&width=256&version=1.1.1&bbox=-46133.17,5675047.40429,580038.965712,6301219.54&service=WMS&format=image%2Fpng&styles=&srs=EPSG%3A25832&request=GetMap&height=256" + ).returns(TEST_TILE) + self.common_tile_req.params["tilematrixset"] = "utm32" with serv: - resp = self.app.get(str(self.common_tile_req)) - eq_(resp.content_type, 'image/png') + resp = app.get(str(self.common_tile_req)) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - def test_get_tile_cascaded_cache(self): - serv = MockServ( - 42423, bbox_aware_query_comparator=True, unordered=True) + def test_get_tile_cascaded_cache(self, app): + serv = MockServ(42423, bbox_aware_query_comparator=True, unordered=True) # gk3 cache requests UTM tiles serv.expects( - '/service?layers=foo,bar&width=256&version=1.1.1&bbox=423495.931784,5596775.88732,501767.448748,5675047.40429&service=WMS&format=image%2Fpng&styles=&srs=EPSG%3A25832&request=GetMap&height=256').returns(TEST_TILE) + "/service?layers=foo,bar&width=256&version=1.1.1&bbox=423495.931784,5596775.88732,501767.448748,5675047.40429&service=WMS&format=image%2Fpng&styles=&srs=EPSG%3A25832&request=GetMap&height=256" + ).returns(TEST_TILE) serv.expects( - '/service?layers=foo,bar&width=256&version=1.1.1&bbox=345224.41482,5596775.88732,423495.931784,5675047.40429&service=WMS&format=image%2Fpng&styles=&srs=EPSG%3A25832&request=GetMap&height=256').returns(TEST_TILE) + "/service?layers=foo,bar&width=256&version=1.1.1&bbox=345224.41482,5596775.88732,423495.931784,5675047.40429&service=WMS&format=image%2Fpng&styles=&srs=EPSG%3A25832&request=GetMap&height=256" + ).returns(TEST_TILE) serv.expects( - '/service?layers=foo,bar&width=256&version=1.1.1&bbox=345224.41482,5518504.37036,423495.931784,5596775.88732&service=WMS&format=image%2Fpng&styles=&srs=EPSG%3A25832&request=GetMap&height=256').returns(TEST_TILE) + "/service?layers=foo,bar&width=256&version=1.1.1&bbox=345224.41482,5518504.37036,423495.931784,5596775.88732&service=WMS&format=image%2Fpng&styles=&srs=EPSG%3A25832&request=GetMap&height=256" + ).returns(TEST_TILE) serv.expects( - '/service?layers=foo,bar&width=256&version=1.1.1&bbox=423495.931784,5518504.37036,501767.448748,5596775.88732&service=WMS&format=image%2Fpng&styles=&srs=EPSG%3A25832&request=GetMap&height=256').returns(TEST_TILE) + "/service?layers=foo,bar&width=256&version=1.1.1&bbox=423495.931784,5518504.37036,501767.448748,5596775.88732&service=WMS&format=image%2Fpng&styles=&srs=EPSG%3A25832&request=GetMap&height=256" + ).returns(TEST_TILE) serv.expects( - '/service?layers=foo,bar&width=256&version=1.1.1&bbox=345224.41482,5440232.8534,423495.931784,5518504.37036&service=WMS&format=image%2Fpng&styles=&srs=EPSG%3A25832&request=GetMap&height=256').returns(TEST_TILE) + "/service?layers=foo,bar&width=256&version=1.1.1&bbox=345224.41482,5440232.8534,423495.931784,5518504.37036&service=WMS&format=image%2Fpng&styles=&srs=EPSG%3A25832&request=GetMap&height=256" + ).returns(TEST_TILE) serv.expects( - '/service?layers=foo,bar&width=256&version=1.1.1&bbox=423495.931784,5440232.8534,501767.448748,5518504.37036&service=WMS&format=image%2Fpng&styles=&srs=EPSG%3A25832&request=GetMap&height=256').returns(TEST_TILE) - self.common_tile_req.params['tilematrixset'] = 'gk3' + "/service?layers=foo,bar&width=256&version=1.1.1&bbox=423495.931784,5440232.8534,501767.448748,5518504.37036&service=WMS&format=image%2Fpng&styles=&srs=EPSG%3A25832&request=GetMap&height=256" + ).returns(TEST_TILE) + self.common_tile_req.params["tilematrixset"] = "gk3" with serv: - resp = self.app.get(str(self.common_tile_req)) - eq_(resp.content_type, 'image/png') + resp = app.get(str(self.common_tile_req)) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_renderd_client.py mapproxy-1.12.0/mapproxy/test/system/test_renderd_client.py --- mapproxy-1.11.0/mapproxy/test/system/test_renderd_client.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_renderd_client.py 2019-08-30 07:34:08.000000000 +0000 @@ -13,39 +13,31 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os +import json -try: - import json; json -except ImportError: - # test skipped later - json = None +import pytest from mapproxy.test.image import img_from_buf from mapproxy.test.http import mock_single_req_httpd -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config -from mapproxy.request.wms import WMS111MapRequest, WMS111FeatureInfoRequest, WMS111CapabilitiesRequest +from mapproxy.test.system import SysTest +from mapproxy.request.wms import ( + WMS111MapRequest, + WMS111FeatureInfoRequest, + WMS111CapabilitiesRequest, +) from mapproxy.test.helper import validate_with_dtd from mapproxy.test.http import mock_httpd from mapproxy.test.image import create_tmp_image from mapproxy.test.system.test_wms import is_111_exception -from mapproxy.util.fs import ensure_directory from mapproxy.cache.renderd import has_renderd_support -from nose.tools import eq_ -from nose.plugins.skip import SkipTest -test_config = {} -base_config = make_base_config(test_config) +@pytest.fixture(scope="module") +def config_file(): + return "renderd_client.yaml" -def setup_module(): - if not has_renderd_support(): - raise SkipTest("requests required") - module_setup(test_config, 'renderd_client.yaml', with_cache_data=True) - -def teardown_module(): - module_teardown(test_config) +pytestmark = pytest.mark.skipif(not has_renderd_support(), reason="requests required") try: from http.server import BaseHTTPRequestHandler @@ -53,74 +45,102 @@ from BaseHTTPServer import BaseHTTPRequestHandler -class TestWMS111(SystemTest): - config = test_config +class TestWMS111(SysTest): def setup(self): - SystemTest.setup(self) - self.common_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1')) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='-180,0,0,80', width='200', height='200', - layers='wms_cache', srs='EPSG:4326', format='image/png', - exceptions='xml', - styles='', request='GetMap')) - self.common_fi_req = WMS111FeatureInfoRequest(url='/service?', - param=dict(x='10', y='20', width='200', height='200', layers='wms_cache', - format='image/png', query_layers='wms_cache', styles='', - bbox='1000,400,2000,1400', srs='EPSG:900913')) - - def test_wms_capabilities(self): - req = WMS111CapabilitiesRequest(url='/service?').copy_with_request_params(self.common_req) - resp = self.app.get(req) - eq_(resp.content_type, 'application/vnd.ogc.wms_xml') + self.common_req = WMS111MapRequest( + url="/service?", param=dict(service="WMS", version="1.1.1") + ) + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="-180,0,0,80", + width="200", + height="200", + layers="wms_cache", + srs="EPSG:4326", + format="image/png", + exceptions="xml", + styles="", + request="GetMap", + ), + ) + self.common_fi_req = WMS111FeatureInfoRequest( + url="/service?", + param=dict( + x="10", + y="20", + width="200", + height="200", + layers="wms_cache", + format="image/png", + query_layers="wms_cache", + styles="", + bbox="1000,400,2000,1400", + srs="EPSG:900913", + ), + ) + + def test_wms_capabilities(self, app): + req = WMS111CapabilitiesRequest(url="/service?").copy_with_request_params( + self.common_req + ) + resp = app.get(req) + assert resp.content_type == "application/vnd.ogc.wms_xml" xml = resp.lxml - eq_(xml.xpath('//GetMap//OnlineResource/@xlink:href', - namespaces=dict(xlink="http://www.w3.org/1999/xlink"))[0], - 'http://localhost/service?') - - layer_names = set(xml.xpath('//Layer/Layer/Name/text()')) - expected_names = set(['direct', 'wms_cache', - 'tms_cache']) - eq_(layer_names, expected_names) - assert validate_with_dtd(xml, dtd_name='wms/1.1.1/WMS_MS_Capabilities.dtd') + assert ( + xml.xpath( + "//GetMap//OnlineResource/@xlink:href", + namespaces=dict(xlink="http://www.w3.org/1999/xlink"), + )[0] + == "http://localhost/service?" + ) + + layer_names = set(xml.xpath("//Layer/Layer/Name/text()")) + expected_names = set(["direct", "wms_cache", "tms_cache"]) + assert layer_names == expected_names + assert validate_with_dtd(xml, dtd_name="wms/1.1.1/WMS_MS_Capabilities.dtd") + + def test_get_map(self, app, cache_dir): - def test_get_map(self): - test_self = self class req_handler(BaseHTTPRequestHandler): + def do_POST(self): - length = int(self.headers['content-length']) + length = int(self.headers["content-length"]) json_data = self.rfile.read(length) - task = json.loads(json_data.decode('utf-8')) - eq_(task['command'], 'tile') + task = json.loads(json_data.decode("utf-8")) + assert task["command"] == "tile" # request main tile of metatile - eq_(task['tiles'], [[15, 17, 5]]) - eq_(task['cache_identifier'], 'wms_cache_GLOBAL_MERCATOR') - eq_(task['priority'], 100) + assert task["tiles"] == [[15, 17, 5]] + assert task["cache_identifier"] == "wms_cache_GLOBAL_MERCATOR" + assert task["priority"] == 100 # this id should not change for the same tile/cache_identifier combination - eq_(task['id'], 'aeb52b506e4e82d0a1edf649d56e0451cfd5862c') + assert task["id"] == "aeb52b506e4e82d0a1edf649d56e0451cfd5862c" # manually create tile renderd should create - tile_filename = os.path.join(test_self.config['cache_dir'], - 'wms_cache_EPSG900913/05/000/000/016/000/000/016.jpeg') - ensure_directory(tile_filename) - with open(tile_filename, 'wb') as f: - f.write(create_tmp_image((256, 256), format='jpeg', color=(255, 0, 100))) + cache_dir.join( + "wms_cache_EPSG900913/05/000/000/016/000/000/016.jpeg" + ).write_binary( + create_tmp_image((256, 256), format="jpeg", color=(255, 0, 100)), + ensure=True, + ) self.send_response(200) - self.send_header('Content-type', 'application/json') + self.send_header("Content-type", "application/json") self.end_headers() self.wfile.write(b'{"status": "ok"}') def log_request(self, code, size=None): pass - with mock_single_req_httpd(('localhost', 42423), req_handler): - self.common_map_req.params['bbox'] = '0,0,9,9' - resp = self.app.get(self.common_map_req) + with mock_single_req_httpd(("localhost", 42423), req_handler): + self.common_map_req.params["bbox"] = "0,0,9,9" + resp = app.get(self.common_map_req) img = img_from_buf(resp.body) - main_color = sorted(img.convert('RGBA').getcolors())[-1] + main_color = sorted(img.convert("RGBA").getcolors())[-1] # check for red color (jpeg/png conversion requires fuzzy comparision) assert main_color[0] == 40000 assert main_color[1][0] > 250 @@ -128,141 +148,157 @@ assert 95 < main_color[1][2] < 105 assert main_color[1][3] == 255 - eq_(resp.content_type, 'image/png') - self.created_tiles.append('wms_cache_EPSG900913/05/000/000/016/000/000/016.jpeg') + assert resp.content_type == "image/png" + assert cache_dir.join( + "wms_cache_EPSG900913/05/000/000/016/000/000/016.jpeg" + ).check() + + def test_get_map_error(self, app): - def test_get_map_error(self): class req_handler(BaseHTTPRequestHandler): + def do_POST(self): - length = int(self.headers['content-length']) + length = int(self.headers["content-length"]) json_data = self.rfile.read(length) - task = json.loads(json_data.decode('utf-8')) - eq_(task['command'], 'tile') + task = json.loads(json_data.decode("utf-8")) + assert task["command"] == "tile" # request main tile of metatile - eq_(task['tiles'], [[15, 17, 5]]) - eq_(task['cache_identifier'], 'wms_cache_GLOBAL_MERCATOR') - eq_(task['priority'], 100) + assert task["tiles"] == [[15, 17, 5]] + assert task["cache_identifier"] == "wms_cache_GLOBAL_MERCATOR" + assert task["priority"] == 100 # this id should not change for the same tile/cache_identifier combination - eq_(task['id'], 'aeb52b506e4e82d0a1edf649d56e0451cfd5862c') + assert task["id"] == "aeb52b506e4e82d0a1edf649d56e0451cfd5862c" self.send_response(200) - self.send_header('Content-type', 'application/json') + self.send_header("Content-type", "application/json") self.end_headers() self.wfile.write(b'{"status": "error", "error_message": "barf"}') def log_request(self, code, size=None): pass - with mock_single_req_httpd(('localhost', 42423), req_handler): - self.common_map_req.params['bbox'] = '0,0,9,9' - resp = self.app.get(self.common_map_req) + with mock_single_req_httpd(("localhost", 42423), req_handler): + self.common_map_req.params["bbox"] = "0,0,9,9" + resp = app.get(self.common_map_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - is_111_exception(resp.lxml, re_msg='Error from renderd: barf') + assert resp.content_type == "application/vnd.ogc.se_xml" + is_111_exception(resp.lxml, re_msg="Error from renderd: barf") - def test_get_map_connection_error(self): - self.common_map_req.params['bbox'] = '0,0,9,9' - resp = self.app.get(self.common_map_req) + def test_get_map_connection_error(self, app): + self.common_map_req.params["bbox"] = "0,0,9,9" + resp = app.get(self.common_map_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - is_111_exception(resp.lxml, re_msg='Error while communicating with renderd:') + assert resp.content_type == "application/vnd.ogc.se_xml" + is_111_exception(resp.lxml, re_msg="Error while communicating with renderd:") + + def test_get_map_non_json_response(self, app): - def test_get_map_non_json_response(self): class req_handler(BaseHTTPRequestHandler): + def do_POST(self): - length = int(self.headers['content-length']) + length = int(self.headers["content-length"]) json_data = self.rfile.read(length) - json.loads(json_data.decode('utf-8')) + json.loads(json_data.decode("utf-8")) self.send_response(200) - self.send_header('Content-type', 'application/json') + self.send_header("Content-type", "application/json") self.end_headers() self.wfile.write(b'{"invalid') def log_request(self, code, size=None): pass - with mock_single_req_httpd(('localhost', 42423), req_handler): - self.common_map_req.params['bbox'] = '0,0,9,9' - resp = self.app.get(self.common_map_req) - - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - is_111_exception(resp.lxml, re_msg='Error while communicating with renderd: invalid JSON') - - - def test_get_featureinfo(self): - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913' - '&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20&feature_count=100'}, - {'body': b'info', 'headers': {'content-type': 'text/plain'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - self.common_fi_req.params['feature_count'] = 100 - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'text/plain') - eq_(resp.body, b'info') + with mock_single_req_httpd(("localhost", 42423), req_handler): + self.common_map_req.params["bbox"] = "0,0,9,9" + resp = app.get(self.common_map_req) + + assert resp.content_type == "application/vnd.ogc.se_xml" + is_111_exception( + resp.lxml, re_msg="Error while communicating with renderd: invalid JSON" + ) + + def test_get_featureinfo(self, app): + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913" + "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20&feature_count=100" + }, + {"body": b"info", "headers": {"content-type": "text/plain"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + self.common_fi_req.params["feature_count"] = 100 + resp = app.get(self.common_fi_req) + assert resp.content_type == "text/plain" + assert resp.body == b"info" + + +class TestTiles(SysTest): -class TestTiles(SystemTest): - config = test_config + def test_get_tile(self, app, cache_dir): - def test_get_tile(self): - test_self = self class req_handler(BaseHTTPRequestHandler): + def do_POST(self): - length = int(self.headers['content-length']) + length = int(self.headers["content-length"]) json_data = self.rfile.read(length) - task = json.loads(json_data.decode('utf-8')) - eq_(task['command'], 'tile') - eq_(task['tiles'], [[10, 20, 6]]) - eq_(task['cache_identifier'], 'tms_cache_GLOBAL_MERCATOR') - eq_(task['priority'], 100) + task = json.loads(json_data.decode("utf-8")) + assert task["command"] == "tile" + assert task["tiles"] == [[10, 20, 6]] + assert task["cache_identifier"] == "tms_cache_GLOBAL_MERCATOR" + assert task["priority"] == 100 # this id should not change for the same tile/cache_identifier combination - eq_(task['id'], 'cf35c1c927158e188d8fbe0db380c1772b536da9') + assert task["id"] == "cf35c1c927158e188d8fbe0db380c1772b536da9" # manually create tile renderd should create - tile_filename = os.path.join(test_self.config['cache_dir'], - 'tms_cache_EPSG900913/06/000/000/010/000/000/020.png') - ensure_directory(tile_filename) - with open(tile_filename, 'wb') as f: - f.write(b"foobaz") + cache_dir.join( + "tms_cache_EPSG900913/06/000/000/010/000/000/020.png" + ).write_binary(b"foobaz", ensure=True) self.send_response(200) - self.send_header('Content-type', 'application/json') + self.send_header("Content-type", "application/json") self.end_headers() self.wfile.write(b'{"status": "ok"}') def log_request(self, code, size=None): pass - with mock_single_req_httpd(('localhost', 42423), req_handler): - resp = self.app.get('/tiles/tms_cache/EPSG900913/6/10/20.png') + with mock_single_req_httpd(("localhost", 42423), req_handler): + resp = app.get("/tiles/tms_cache/EPSG900913/6/10/20.png") + + assert resp.content_type == "image/png" + assert resp.body == b"foobaz" + assert cache_dir.join( + "tms_cache_EPSG900913/06/000/000/010/000/000/020.png" + ).check() - eq_(resp.content_type, 'image/png') - eq_(resp.body, b'foobaz') - self.created_tiles.append('tms_cache_EPSG900913/06/000/000/010/000/000/020.png') + def test_get_tile_error(self, app): - def test_get_tile_error(self): class req_handler(BaseHTTPRequestHandler): + def do_POST(self): - length = int(self.headers['content-length']) + length = int(self.headers["content-length"]) json_data = self.rfile.read(length) - task = json.loads(json_data.decode('utf-8')) - eq_(task['command'], 'tile') - eq_(task['tiles'], [[10, 20, 7]]) - eq_(task['cache_identifier'], 'tms_cache_GLOBAL_MERCATOR') - eq_(task['priority'], 100) + task = json.loads(json_data.decode("utf-8")) + assert task["command"] == "tile" + assert task["tiles"] == [[10, 20, 7]] + assert task["cache_identifier"] == "tms_cache_GLOBAL_MERCATOR" + assert task["priority"] == 100 # this id should not change for the same tile/cache_identifier combination - eq_(task['id'], 'c24b8c3247afec34fd0a53e5d3706e977877ef47') + assert task["id"] == "c24b8c3247afec34fd0a53e5d3706e977877ef47" self.send_response(200) - self.send_header('Content-type', 'application/json') + self.send_header("Content-type", "application/json") self.end_headers() - self.wfile.write(b'{"status": "error", "error_message": "you told me to fail"}') + self.wfile.write( + b'{"status": "error", "error_message": "you told me to fail"}' + ) def log_request(self, code, size=None): pass - with mock_single_req_httpd(('localhost', 42423), req_handler): - resp = self.app.get('/tiles/tms_cache/EPSG900913/7/10/20.png', status=500) - eq_(resp.content_type, 'text/plain') - eq_(resp.body, b'Error from renderd: you told me to fail') + with mock_single_req_httpd(("localhost", 42423), req_handler): + resp = app.get("/tiles/tms_cache/EPSG900913/7/10/20.png", status=500) + assert resp.content_type == "text/plain" + assert resp.body == b"Error from renderd: you told me to fail" diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_scalehints.py mapproxy-1.12.0/mapproxy/test/system/test_scalehints.py --- mapproxy-1.11.0/mapproxy/test/system/test_scalehints.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_scalehints.py 2019-08-30 07:34:08.000000000 +0000 @@ -16,97 +16,125 @@ from __future__ import division import math +import pytest from mapproxy.request.wms import ( - WMS111MapRequest, WMS111CapabilitiesRequest, WMS130CapabilitiesRequest + WMS111MapRequest, + WMS111CapabilitiesRequest, + WMS130CapabilitiesRequest, ) -from mapproxy.test.system import module_setup, module_teardown, make_base_config, SystemTest +from mapproxy.test.system import SysTest from mapproxy.test.image import is_png, is_transparent, tmp_image from mapproxy.test.http import mock_httpd from mapproxy.test.system.test_wms import is_111_capa, is_130_capa, ns130 -from nose.tools import assert_almost_equal -test_config = {} -def setup_module(): - module_setup(test_config, 'scalehints.yaml') +@pytest.fixture(scope="module") +def config_file(): + return "scalehints.yaml" -def teardown_module(): - module_teardown(test_config) - -base_config = make_base_config(test_config) def diagonal_res_to_pixel_res(res): """ >>> '%.2f' % round(diagonal_res_to_pixel_res(14.14214), 4) '10.00' """ - return math.sqrt((float(res)**2)/2) + return math.sqrt((float(res) ** 2) / 2) + + +class TestWMS(SysTest): -class TestWMS(SystemTest): - config = test_config def setup(self): - SystemTest.setup(self) - self.common_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1')) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='-180,0,0,80', width='200', height='200', - layers='res', srs='EPSG:4326', format='image/png', transparent='true', - styles='', request='GetMap')) - - def test_capabilities_111(self): - req = WMS111CapabilitiesRequest(url='/service?').copy_with_request_params(self.common_req) - resp = self.app.get(req) + self.common_req = WMS111MapRequest( + url="/service?", param=dict(service="WMS", version="1.1.1") + ) + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="-180,0,0,80", + width="200", + height="200", + layers="res", + srs="EPSG:4326", + format="image/png", + transparent="true", + styles="", + request="GetMap", + ), + ) + + def test_capabilities_111(self, app): + req = WMS111CapabilitiesRequest(url="/service?").copy_with_request_params( + self.common_req + ) + resp = app.get(req) xml = resp.lxml assert is_111_capa(xml) - hints = xml.xpath('//Layer/Layer/ScaleHint') - assert_almost_equal(diagonal_res_to_pixel_res(hints[0].attrib['min']), 10, 2) - assert_almost_equal(diagonal_res_to_pixel_res(hints[0].attrib['max']), 10000, 2) - - assert_almost_equal(diagonal_res_to_pixel_res(hints[1].attrib['min']), 2.8, 2) - assert_almost_equal(diagonal_res_to_pixel_res(hints[1].attrib['max']), 280, 2) - - assert_almost_equal(diagonal_res_to_pixel_res(hints[2].attrib['min']), 0.28, 2) - assert_almost_equal(diagonal_res_to_pixel_res(hints[2].attrib['max']), 2.8, 2) - - def test_capabilities_130(self): - req = WMS130CapabilitiesRequest(url='/service?').copy_with_request_params(self.common_req) - resp = self.app.get(req) + hints = xml.xpath("//Layer/Layer/ScaleHint") + assert diagonal_res_to_pixel_res(hints[0].attrib["min"]) == pytest.approx(10) + assert diagonal_res_to_pixel_res(hints[0].attrib["max"]) == pytest.approx(10000) + + assert diagonal_res_to_pixel_res(hints[1].attrib["min"]) == pytest.approx(2.8) + assert diagonal_res_to_pixel_res(hints[1].attrib["max"]) == pytest.approx(280) + + assert diagonal_res_to_pixel_res(hints[2].attrib["min"]) == pytest.approx(0.28) + assert diagonal_res_to_pixel_res(hints[2].attrib["max"]) == pytest.approx(2.8) + + def test_capabilities_130(self, app): + req = WMS130CapabilitiesRequest(url="/service?").copy_with_request_params( + self.common_req + ) + resp = app.get(req) xml = resp.lxml assert is_130_capa(xml) - min_scales = xml.xpath('//wms:Layer/wms:Layer/wms:MinScaleDenominator/text()', namespaces=ns130) - max_scales = xml.xpath('//wms:Layer/wms:Layer/wms:MaxScaleDenominator/text()', namespaces=ns130) + min_scales = xml.xpath( + "//wms:Layer/wms:Layer/wms:MinScaleDenominator/text()", namespaces=ns130 + ) + max_scales = xml.xpath( + "//wms:Layer/wms:Layer/wms:MaxScaleDenominator/text()", namespaces=ns130 + ) + + assert float(min_scales[0]) == pytest.approx(35714.28) + assert float(max_scales[0]) == pytest.approx(35714285.7) - assert_almost_equal(float(min_scales[0]), 35714.28, 1) - assert_almost_equal(float(max_scales[0]), 35714285.7, 1) + assert float(min_scales[1]) == pytest.approx(10000) + assert float(max_scales[1]) == pytest.approx(1000000) - assert_almost_equal(float(min_scales[1]), 10000, 2) - assert_almost_equal(float(max_scales[1]), 1000000, 2) + assert float(min_scales[2]) == pytest.approx(1000) + assert float(max_scales[2]) == pytest.approx(10000) - assert_almost_equal(float(min_scales[2]), 1000, 2) - assert_almost_equal(float(max_scales[2]), 10000, 2) - - def test_get_map_above_res(self): + def test_get_map_above_res(self, app): # no layer rendered - resp = self.app.get(self.common_map_req) + resp = app.get(self.common_map_req) assert is_png(resp.body) assert is_transparent(resp.body) - def test_get_map_mixed(self): + def test_get_map_mixed(self, app, cache_dir): # only res layer matches resolution range - self.common_map_req.params['layers'] = 'res,scale' - self.common_map_req.params['bbox'] = '0,0,100000,100000' - self.common_map_req.params['srs'] = 'EPSG:900913' + self.common_map_req.params["layers"] = "res,scale" + self.common_map_req.params["bbox"] = "0,0,100000,100000" + self.common_map_req.params["srs"] = "EPSG:900913" self.common_map_req.params.size = 100, 100 - self.created_tiles.append('res_cache_EPSG900913/08/000/000/128/000/000/128.jpeg') - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/service?LAYERs=reslayer&SERVICE=WMS&FORMAT=image%2Fjpeg' - '&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=' - '&VERSION=1.1.1&BBOX=0.0,0.0,156543.033928,156543.033928' - '&WIDTH=256'}, - {'body': img.read(), 'headers': {'content-type': 'image/png'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - resp = self.app.get(self.common_map_req) + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + { + "path": r"/service?LAYERs=reslayer&SERVICE=WMS&FORMAT=image%2Fjpeg" + "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=" + "&VERSION=1.1.1&BBOX=0.0,0.0,156543.033928,156543.033928" + "&WIDTH=256" + }, + {"body": img.read(), "headers": {"content-type": "image/png"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + resp = app.get(self.common_map_req) assert is_png(resp.body) assert not is_transparent(resp.body) + + assert cache_dir.join( + "res_cache_EPSG900913/08/000/000/128/000/000/128.jpeg" + ).check() diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_seed_only.py mapproxy-1.12.0/mapproxy/test/system/test_seed_only.py --- mapproxy-1.11.0/mapproxy/test/system/test_seed_only.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_seed_only.py 2019-08-30 07:34:08.000000000 +0000 @@ -16,67 +16,78 @@ from __future__ import division from io import BytesIO + +import pytest + from mapproxy.request.wms import WMS111MapRequest from mapproxy.compat.image import Image from mapproxy.test.image import is_png, is_jpeg -from mapproxy.test.system import module_setup, module_teardown, SystemTest -from nose.tools import eq_ +from mapproxy.test.system import SysTest -test_config = {} -def setup_module(): - module_setup(test_config, 'seedonly.yaml', with_cache_data=True) +@pytest.fixture(scope="module") +def config_file(): + return "seedonly.yaml" -def teardown_module(): - module_teardown(test_config) -class TestSeedOnlyWMS(SystemTest): - config = test_config +class TestSeedOnlyWMS(SysTest): + def setup(self): - SystemTest.setup(self) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='-180,0,0,80', width='200', height='200', - layers='wms_cache', srs='EPSG:4326', format='image/png', - styles='', request='GetMap', transparent=True)) - - def test_get_map_cached(self): - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="-180,0,0,80", + width="200", + height="200", + layers="wms_cache", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + transparent=True, + ), + ) + + def test_get_map_cached(self, app, fixture_cache_data): + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) - eq_(img.mode, 'RGB') + assert img.mode == "RGB" # cached image has more that 256 colors, getcolors -> None - eq_(img.getcolors(), None) + assert img.getcolors() == None - def test_get_map_uncached(self): - self.common_map_req.params['bbox'] = '10,10,20,20' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_get_map_uncached(self, app): + self.common_map_req.params["bbox"] = "10,10,20,20" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) - eq_(img.mode, 'RGBA') - eq_(img.getcolors(), [(200*200, (255, 255, 255, 0))]) + assert img.mode == "RGBA" + assert img.getcolors() == [(200 * 200, (255, 255, 255, 0))] + -class TestSeedOnlyTMS(SystemTest): - config = test_config +class TestSeedOnlyTMS(SysTest): - def test_get_tile_cached(self): - resp = self.app.get('/tms/1.0.0/wms_cache/0/0/1.jpeg') - eq_(resp.content_type, 'image/jpeg') + def test_get_tile_cached(self, app, fixture_cache_data): + resp = app.get("/tms/1.0.0/wms_cache/0/0/1.jpeg") + assert resp.content_type == "image/jpeg" data = BytesIO(resp.body) assert is_jpeg(data) img = Image.open(data) - eq_(img.mode, 'RGB') + assert img.mode == "RGB" # cached image has more that 256 colors, getcolors -> None - eq_(img.getcolors(), None) + assert img.getcolors() == None - def test_get_tile_uncached(self): - resp = self.app.get('/tms/1.0.0/wms_cache/0/0/0.jpeg') - eq_(resp.content_type, 'image/png') + def test_get_tile_uncached(self, app): + resp = app.get("/tms/1.0.0/wms_cache/0/0/0.jpeg") + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) - eq_(img.mode, 'RGBA') - eq_(img.getcolors(), [(256*256, (255, 255, 255, 0))]) \ No newline at end of file + assert img.mode == "RGBA" + assert img.getcolors() == [(256 * 256, (255, 255, 255, 0))] diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_seed.py mapproxy-1.12.0/mapproxy/test/system/test_seed.py --- mapproxy-1.11.0/mapproxy/test/system/test_seed.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_seed.py 2019-08-30 07:34:08.000000000 +0000 @@ -17,6 +17,7 @@ import time import shutil import tempfile + from mapproxy.config.loader import load_configuration from mapproxy.cache.tile import Tile from mapproxy.image import ImageSource @@ -27,10 +28,10 @@ from mapproxy.config import local_base_config from mapproxy.util.fs import ensure_directory +from mapproxy.test.helper import assert_files_in_dir from mapproxy.test.http import mock_httpd from mapproxy.test.image import tmp_image, create_tmp_image_buf, create_tmp_image -from nose.tools import eq_ FIXTURE_DIR = os.path.join(os.path.dirname(__file__), 'fixture') @@ -151,7 +152,7 @@ assert self.tile_exists((0, 0, 2)) assert not self.tile_exists((0, 0, 3)) - eq_(sorted(os.listdir(os.path.join(self.dir, 'cache', 'one_EPSG4326'))), + assert_files_in_dir(os.path.join(self.dir, 'cache', 'one_EPSG4326'), ['02']) def test_cleanup_remove_all(self): @@ -166,7 +167,7 @@ self.make_tile((0, 0, 2)) self.make_tile((0, 0, 3)) - eq_(sorted(os.listdir(os.path.join(self.dir, 'cache', 'one_EPSG4326'))), + assert_files_in_dir(os.path.join(self.dir, 'cache', 'one_EPSG4326'), ['00', '01', '02', '03']) cleanup(cleanup_tasks, verbose=False, dry_run=False) @@ -180,7 +181,7 @@ assert self.tile_exists((0, 0, 3)) # remove_all should remove the whole directory - eq_(sorted(os.listdir(os.path.join(self.dir, 'cache', 'one_EPSG4326'))), + assert_files_in_dir(os.path.join(self.dir, 'cache', 'one_EPSG4326'), ['00', '02', '03']) def test_cleanup_coverage(self): @@ -261,14 +262,16 @@ assert cache.is_cached(Tile((0, 0, 2))) assert cache.is_cached(Tile((0, 0, 3))) - eq_(sorted(os.listdir(os.path.join(self.dir, 'cache', 'sqlite_cache', 'GLOBAL_GEODETIC'))), - ['2.mbtile', '3.mbtile']) + assert_files_in_dir(os.path.join(self.dir, 'cache', 'sqlite_cache', 'GLOBAL_GEODETIC'), + ['2.mbtile', '3.mbtile'], + glob='*.mbtile') cleanup(cleanup_tasks, verbose=False, dry_run=False) # 3.mbtile file is still there - eq_(sorted(os.listdir(os.path.join(self.dir, 'cache', 'sqlite_cache', 'GLOBAL_GEODETIC'))), - ['2.mbtile', '3.mbtile']) + assert_files_in_dir(os.path.join(self.dir, 'cache', 'sqlite_cache', 'GLOBAL_GEODETIC'), + ['2.mbtile', '3.mbtile'], + glob='*.mbtile') assert cache.is_cached(Tile((0, 0, 2))) assert not cache.is_cached(Tile((0, 0, 3))) @@ -282,14 +285,16 @@ assert cache.is_cached(Tile((0, 0, 2))) assert cache.is_cached(Tile((0, 0, 3))) - eq_(sorted(os.listdir(os.path.join(self.dir, 'cache', 'sqlite_cache', 'GLOBAL_GEODETIC'))), - ['2.mbtile', '3.mbtile']) + assert_files_in_dir(os.path.join(self.dir, 'cache', 'sqlite_cache', 'GLOBAL_GEODETIC'), + ['2.mbtile', '3.mbtile'], + glob='*.mbtile') cleanup(cleanup_tasks, verbose=False, dry_run=False) # 3.mbtile file should be removed completely - eq_(sorted(os.listdir(os.path.join(self.dir, 'cache', 'sqlite_cache', 'GLOBAL_GEODETIC'))), - ['3.mbtile']) + assert_files_in_dir(os.path.join(self.dir, 'cache', 'sqlite_cache', 'GLOBAL_GEODETIC'), + ['3.mbtile'], + glob='*.mbtile') assert not cache.is_cached(Tile((0, 0, 2))) assert cache.is_cached(Tile((0, 0, 3))) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_sld.py mapproxy-1.12.0/mapproxy/test/system/test_sld.py --- mapproxy-1.11.0/mapproxy/test/system/test_sld.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_sld.py 2019-08-30 07:34:08.000000000 +0000 @@ -14,8 +14,8 @@ # limitations under the License. from __future__ import division -import os -import tempfile + +import pytest try: from urllib.parse import quote @@ -23,66 +23,98 @@ from urllib import quote from mapproxy.request.wms import WMS111MapRequest -from mapproxy.test.system import module_setup, module_teardown, make_base_config, SystemTest +from mapproxy.test.system import SysTest from mapproxy.test.http import mock_httpd from mapproxy.test.image import create_tmp_image -from nose.tools import eq_ -test_config = {} +@pytest.fixture(scope="module") +def config_file(): + return "sld.yaml" + -def setup_module(): - test_config['base_dir'] = tempfile.mkdtemp() - with open(os.path.join(test_config['base_dir'], 'mysld.xml'), 'wb') as f: - f.write(b'') - module_setup(test_config, 'sld.yaml') +TESTSERVER_ADDRESS = "localhost", 42423 -def teardown_module(): - module_teardown(test_config) -base_config = make_base_config(test_config) +class TestWMS(SysTest): -TESTSERVER_ADDRESS = 'localhost', 42423 + @pytest.fixture(scope="class") + def additional_files(self, base_dir): + base_dir.join("mysld.xml").write("") -class TestWMS(SystemTest): - config = test_config def setup(self): - SystemTest.setup(self) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='0,0,10,10', width='200', height='200', - srs='EPSG:4326', format='image/png', styles='', request='GetMap', - exceptions='xml')) - self.common_wms_url = ("/service?styles=&srs=EPSG%3A4326&version=1.1.1&" + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="0,0,10,10", + width="200", + height="200", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + exceptions="xml", + ), + ) + self.common_wms_url = ( + "/service?styles=&srs=EPSG%3A4326&version=1.1.1&" "bbox=0.0,0.0,10.0,10.0&service=WMS&format=image%2Fpng&request=GetMap" - "&width=200&height=200") - - def test_sld_url(self): - self.common_map_req.params['layers'] = 'sld_url' - with mock_httpd(TESTSERVER_ADDRESS, [ - ({'path': self.common_wms_url + '&sld=' +quote('http://example.org/sld.xml'), - 'method': 'GET'}, - {'body': create_tmp_image((200, 200), format='png')} - )]): - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') - - def test_sld_file(self): - self.common_map_req.params['layers'] = 'sld_file' - with mock_httpd(TESTSERVER_ADDRESS, [ - ({'path': self.common_wms_url + '&sld_body=' +quote(''), 'method': 'GET'}, - {'body': create_tmp_image((200, 200), format='png')} - )]): - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') - - def test_sld_body(self): - self.common_map_req.params['layers'] = 'sld_body' - with mock_httpd(TESTSERVER_ADDRESS, [ - ({'path': self.common_wms_url + '&sld_body=' +quote(''), - 'method': 'POST'}, - {'body': create_tmp_image((200, 200), format='png')} - )]): - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') - + "&width=200&height=200" + ) + def test_sld_url(self, app): + self.common_map_req.params["layers"] = "sld_url" + with mock_httpd( + TESTSERVER_ADDRESS, + [ + ( + { + "path": self.common_wms_url + + "&sld=" + + quote("http://example.org/sld.xml"), + "method": "GET", + }, + {"body": create_tmp_image((200, 200), format="png")}, + ) + ], + ): + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" + + def test_sld_file(self, app): + self.common_map_req.params["layers"] = "sld_file" + with mock_httpd( + TESTSERVER_ADDRESS, + [ + ( + { + "path": self.common_wms_url + "&sld_body=" + quote(""), + "method": "GET", + }, + {"body": create_tmp_image((200, 200), format="png")}, + ) + ], + ): + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" + + def test_sld_body(self, app): + self.common_map_req.params["layers"] = "sld_body" + with mock_httpd( + TESTSERVER_ADDRESS, + [ + ( + { + "path": self.common_wms_url + + "&sld_body=" + + quote(""), + "method": "POST", + }, + {"body": create_tmp_image((200, 200), format="png")}, + ) + ], + ): + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_source_errors.py mapproxy-1.12.0/mapproxy/test/system/test_source_errors.py --- mapproxy-1.11.0/mapproxy/test/system/test_source_errors.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_source_errors.py 2019-08-30 07:34:08.000000000 +0000 @@ -15,242 +15,367 @@ from __future__ import division -import os +import pytest from mapproxy.request.wms import WMS111MapRequest -from mapproxy.test.image import is_transparent, create_tmp_image, bgcolor_ratio, img_from_buf, assert_colors_equal +from mapproxy.test.image import ( + is_transparent, + create_tmp_image, + bgcolor_ratio, + img_from_buf, + assert_colors_equal, +) from mapproxy.test.http import mock_httpd -from mapproxy.test.system import module_setup, module_teardown, SystemTest +from mapproxy.test.system import SysTest from mapproxy.test.system.test_wms import is_111_exception -from nose.tools import eq_ -test_config = {} -test_config_raise = {} -def setup_module(): - module_setup(test_config, 'source_errors.yaml') - module_setup(test_config_raise, 'source_errors_raise.yaml') +transp = create_tmp_image((200, 200), mode="RGBA", color=(0, 0, 0, 0)) -def teardown_module(): - module_teardown(test_config) - module_teardown(test_config_raise) +class TestWMS(SysTest): -transp = create_tmp_image((200, 200), mode='RGBA', color=(0, 0, 0, 0)) + @pytest.fixture(scope="class") + def config_file(self): + return "source_errors.yaml" -class TestWMS(SystemTest): - config = test_config def setup(self): - SystemTest.setup(self) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='9,50,10,51', width='200', height='200', - layers='online', srs='EPSG:4326', format='image/png', - styles='', request='GetMap', transparent=True)) - - def test_online(self): - common_params = (r'?SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0' - '&WIDTH=200&transparent=True') - - expected_req = [({'path': '/service_a' + common_params + '&layers=a_one'}, - {'body': transp, 'headers': {'content-type': 'image/png'}}), - ] - - with mock_httpd(('localhost', 42423), expected_req): - self.common_map_req.params.layers = 'online' - resp = self.app.get(self.common_map_req) - assert 'Cache-Control' not in resp.headers - eq_(resp.content_type, 'image/png') + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="9,50,10,51", + width="200", + height="200", + layers="online", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + transparent=True, + ), + ) + + def test_online(self, app): + common_params = ( + r"?SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0" + "&WIDTH=200&transparent=True" + ) + + expected_req = [ + ( + {"path": "/service_a" + common_params + "&layers=a_one"}, + {"body": transp, "headers": {"content-type": "image/png"}}, + ) + ] + + with mock_httpd(("localhost", 42423), expected_req): + self.common_map_req.params.layers = "online" + resp = app.get(self.common_map_req) + assert "Cache-Control" not in resp.headers + assert resp.content_type == "image/png" assert is_transparent(resp.body) - def test_mixed_layer_source(self): - common_params = (r'?SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0' - '&WIDTH=200&transparent=True') - - expected_req = [({'path': '/service_a' + common_params + '&layers=a_one'}, - {'body': transp, 'headers': {'content-type': 'image/png'}}), - ] - - with mock_httpd(('localhost', 42423), expected_req): - self.common_map_req.params.layers = 'mixed' - resp = self.app.get(self.common_map_req) + def test_mixed_layer_source(self, app): + common_params = ( + r"?SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0" + "&WIDTH=200&transparent=True" + ) + + expected_req = [ + ( + {"path": "/service_a" + common_params + "&layers=a_one"}, + {"body": transp, "headers": {"content-type": "image/png"}}, + ) + ] + + with mock_httpd(("localhost", 42423), expected_req): + self.common_map_req.params.layers = "mixed" + resp = app.get(self.common_map_req) assert_no_cache(resp) - eq_(resp.content_type, 'image/png') + assert resp.content_type == "image/png" assert 0.99 > bgcolor_ratio(resp.body) > 0.95 - def test_mixed_sources(self): - common_params = (r'?SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0' - '&WIDTH=200&transparent=True') - - expected_req = [({'path': '/service_a' + common_params + '&layers=a_one'}, - {'body': transp, 'headers': {'content-type': 'image/png'}}), - ] - - with mock_httpd(('localhost', 42423), expected_req): - self.common_map_req.params.layers = 'online,all_offline' - resp = self.app.get(self.common_map_req) + def test_mixed_sources(self, app): + common_params = ( + r"?SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0" + "&WIDTH=200&transparent=True" + ) + + expected_req = [ + ( + {"path": "/service_a" + common_params + "&layers=a_one"}, + {"body": transp, "headers": {"content-type": "image/png"}}, + ) + ] + + with mock_httpd(("localhost", 42423), expected_req): + self.common_map_req.params.layers = "online,all_offline" + resp = app.get(self.common_map_req) assert_no_cache(resp) - eq_(resp.content_type, 'image/png') + assert resp.content_type == "image/png" assert 0.99 > bgcolor_ratio(resp.body) > 0.95 # open('/tmp/foo.png', 'wb').write(resp.body) - def test_all_offline(self): - self.common_map_req.params.layers = 'all_offline' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - is_111_exception(resp.lxml, re_msg='no response from url') + def test_all_offline(self, app): + self.common_map_req.params.layers = "all_offline" + resp = app.get(self.common_map_req) + assert resp.content_type == "application/vnd.ogc.se_xml" + is_111_exception(resp.lxml, re_msg="no response from url") -class TestWMSRaise(SystemTest): - config = test_config_raise +class TestWMSRaise(SysTest): + + @pytest.fixture(scope="class") + def config_file(self): + return "source_errors_raise.yaml" + def setup(self): - SystemTest.setup(self) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='9,50,10,51', width='200', height='200', - layers='online', srs='EPSG:4326', format='image/png', - styles='', request='GetMap', transparent=True)) - - def test_mixed_layer_source(self): - common_params = (r'?SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0' - '&WIDTH=200&transparent=True') - - expected_req = [({'path': '/service_a' + common_params + '&layers=a_one'}, - {'body': transp, 'headers': {'content-type': 'image/png'}}), - ] - - with mock_httpd(('localhost', 42423), expected_req): - self.common_map_req.params.layers = 'mixed' - resp = self.app.get(self.common_map_req) - is_111_exception(resp.lxml, re_msg='no response from url') - - def test_all_offline(self): - self.common_map_req.params.layers = 'all_offline' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - is_111_exception(resp.lxml, re_msg='no response from url') + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="9,50,10,51", + width="200", + height="200", + layers="online", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + transparent=True, + ), + ) + + def test_mixed_layer_source(self, app): + common_params = ( + r"?SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=9.0,50.0,10.0,51.0" + "&WIDTH=200&transparent=True" + ) + + expected_req = [ + ( + {"path": "/service_a" + common_params + "&layers=a_one"}, + {"body": transp, "headers": {"content-type": "image/png"}}, + ) + ] + + with mock_httpd(("localhost", 42423), expected_req): + self.common_map_req.params.layers = "mixed" + resp = app.get(self.common_map_req) + is_111_exception(resp.lxml, re_msg="no response from url") + + def test_all_offline(self, app): + self.common_map_req.params.layers = "all_offline" + resp = app.get(self.common_map_req) + assert resp.content_type == "application/vnd.ogc.se_xml" + is_111_exception(resp.lxml, re_msg="no response from url") + + +class TestTileErrors(SysTest): + + @pytest.fixture(scope="class") + def config_file(self): + return "source_errors.yaml" -class TestTileErrors(SystemTest): - config = test_config def setup(self): - SystemTest.setup(self) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='0,-90,180,90', width='250', height='250', - layers='tilesource', srs='EPSG:4326', format='image/png', - styles='', request='GetMap', transparent=True)) - - self.common_tile_req = '/tiles/tilesource/EPSG4326/1/1/0.png' - - def test_wms_uncached_response(self): - expected_req = [({'path': '/foo/1/1/0.png'}, - {'body': b'not found', 'status': 404, 'headers': {'content-type': 'text/plain'}}), - ] - - with mock_httpd(('localhost', 42423), expected_req): - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') - assert_no_cache(resp) - img = img_from_buf(resp.body) - eq_(img.getcolors(), [(250 * 250, (255, 0, 128))]) - assert not os.path.exists(os.path.join(self.base_config().cache.base_dir, - 'tilesource_cache_EPSG4326/01/000/000/001/000/000/000.png')) - - def test_wms_cached_response(self): - expected_req = [({'path': '/foo/1/1/0.png'}, - {'body': b'no content', 'status': 204, 'headers': {'content-type': 'text/plain'}}), - ] - - with mock_httpd(('localhost', 42423), expected_req): - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') - assert 'Cache-Control' not in resp.headers + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="0,-90,180,90", + width="250", + height="250", + layers="tilesource", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + transparent=True, + ), + ) + + self.common_tile_req = "/tiles/tilesource/EPSG4326/1/1/0.png" + + def test_wms_uncached_response(self, app, cache_dir): + expected_req = [ + ( + {"path": "/foo/1/1/0.png"}, + { + "body": b"not found", + "status": 404, + "headers": {"content-type": "text/plain"}, + }, + ) + ] + + with mock_httpd(("localhost", 42423), expected_req): + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" + assert_no_cache(resp) + img = img_from_buf(resp.body) + assert img.getcolors() == [(250 * 250, (255, 0, 128))] + assert not cache_dir.join( + "tilesource_cache_EPSG4326/01/000/000/001/000/000/000.png" + ).check() + + def test_wms_cached_response(self, app, cache_dir): + expected_req = [ + ( + {"path": "/foo/1/1/0.png"}, + { + "body": b"no content", + "status": 204, + "headers": {"content-type": "text/plain"}, + }, + ) + ] + + with mock_httpd(("localhost", 42423), expected_req): + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" + assert "Cache-Control" not in resp.headers img = img_from_buf(resp.body) assert_colors_equal(img, [(250 * 250, (100, 200, 50, 250))]) - self.created_tiles.append('tilesource_cache_EPSG4326/01/000/000/001/000/000/000.png') + assert cache_dir.join( + "tilesource_cache_EPSG4326/01/000/000/001/000/000/000.png" + ).check() + + def test_wms_unhandled_error_code(self, app): + expected_req = [ + ( + {"path": "/foo/1/1/0.png"}, + { + "body": b"error", + "status": 500, + "headers": {"content-type": "text/plain"}, + }, + ) + ] + + with mock_httpd(("localhost", 42423), expected_req): + resp = app.get(self.common_map_req) + assert "Cache-Control" not in resp.headers + assert resp.content_type == "application/vnd.ogc.se_xml" + assert b"500" in resp.body + + def test_wms_catchall_error_no_image_response(self, app): + expected_req = [ + ( + {"path": "/foo/1/1/0.png"}, + { + "body": b"error", + "status": 200, + "headers": {"content-type": "text/plain"}, + }, + ) + ] + + with mock_httpd(("localhost", 42423), expected_req): + self.common_map_req.params["layers"] = "tilesource_catchall" + resp = app.get(self.common_map_req) + assert_no_cache(resp) + assert resp.content_type == "image/png" + img = img_from_buf(resp.body) + assert img.getcolors() == [(250 * 250, (100, 50, 50))] - def test_wms_unhandled_error_code(self): - expected_req = [({'path': '/foo/1/1/0.png'}, - {'body': b'error', 'status': 500, 'headers': {'content-type': 'text/plain'}}), - ] - - with mock_httpd(('localhost', 42423), expected_req): - resp = self.app.get(self.common_map_req) - assert 'Cache-Control' not in resp.headers - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - assert b'500' in resp.body - - def test_wms_catchall_error_no_image_response(self): - expected_req = [({'path': '/foo/1/1/0.png'}, - {'body': b'error', 'status': 200, 'headers': {'content-type': 'text/plain'}}), - ] - - with mock_httpd(('localhost', 42423), expected_req): - self.common_map_req.params['layers'] = 'tilesource_catchall' - resp = self.app.get(self.common_map_req) - assert_no_cache(resp) - eq_(resp.content_type, 'image/png') - img = img_from_buf(resp.body) - eq_(img.getcolors(), [(250 * 250, (100, 50, 50))]) - - def test_tile_uncached_response(self): - expected_req = [({'path': '/foo/1/1/0.png'}, - {'body': b'not found', 'status': 404, 'headers': {'content-type': 'text/plain'}}), - ] - - with mock_httpd(('localhost', 42423), expected_req): - resp = self.app.get(self.common_tile_req) - assert_no_cache(resp) - eq_(resp.content_type, 'image/png') - img = img_from_buf(resp.body) - eq_(img.getcolors(), [(256 * 256, (255, 0, 128))]) - assert not os.path.exists(os.path.join(self.base_config().cache.base_dir, - 'tilesource_cache_EPSG4326/01/000/000/001/000/000/000.png')) - - def test_tile_cached_response(self): - expected_req = [({'path': '/foo/1/1/0.png'}, - {'body': b'no content', 'status': 204, 'headers': {'content-type': 'text/plain'}}), - ] - - with mock_httpd(('localhost', 42423), expected_req): - resp = self.app.get(self.common_tile_req) - assert 'public' in resp.headers['Cache-Control'] - eq_(resp.content_type, 'image/png') - img = img_from_buf(resp.body) - eq_(img.getcolors(), [(256 * 256, (100, 200, 50, 250))]) - self.created_tiles.append('tilesource_cache_EPSG4326/01/000/000/001/000/000/000.png') - - def test_tile_unhandled_error_code(self): - expected_req = [({'path': '/foo/1/1/0.png'}, - {'body': b'error', 'status': 500, 'headers': {'content-type': 'text/plain'}}), - ] - - with mock_httpd(('localhost', 42423), expected_req): - resp = self.app.get(self.common_tile_req, status=500) - assert 'Cache-Control' not in resp.headers - # no assert_no_cache(resp): returns XML exception that bypasses cache control setting - eq_(resp.content_type, 'text/plain') - assert b'500' in resp.body + def test_tile_uncached_response(self, app, cache_dir): + expected_req = [ + ( + {"path": "/foo/1/1/0.png"}, + { + "body": b"not found", + "status": 404, + "headers": {"content-type": "text/plain"}, + }, + ) + ] - def test_tile_catchall_error_no_image_response(self): - expected_req = [({'path': '/foo/1/1/0.png'}, - {'body': b'error', 'status': 200, 'headers': {'content-type': 'text/plain'}}), - ] + with mock_httpd(("localhost", 42423), expected_req): + resp = app.get(self.common_tile_req) + assert_no_cache(resp) + assert resp.content_type == "image/png" + img = img_from_buf(resp.body) + assert img.getcolors() == [(256 * 256, (255, 0, 128))] + assert not cache_dir.join( + "tilesource_cache_EPSG4326/01/000/000/001/000/000/000.png" + ).check() + + def test_tile_cached_response(self, app, cache_dir): + expected_req = [ + ( + {"path": "/foo/1/1/0.png"}, + { + "body": b"no content", + "status": 204, + "headers": {"content-type": "text/plain"}, + }, + ) + ] + + with mock_httpd(("localhost", 42423), expected_req): + resp = app.get(self.common_tile_req) + assert "public" in resp.headers["Cache-Control"] + assert resp.content_type == "image/png" + img = img_from_buf(resp.body) + assert img.getcolors() == [(256 * 256, (100, 200, 50, 250))] + assert cache_dir.join( + "tilesource_cache_EPSG4326/01/000/000/001/000/000/000.png" + ).check() + + def test_tile_unhandled_error_code(self, app): + expected_req = [ + ( + {"path": "/foo/1/1/0.png"}, + { + "body": b"error", + "status": 500, + "headers": {"content-type": "text/plain"}, + }, + ) + ] + + with mock_httpd(("localhost", 42423), expected_req): + resp = app.get(self.common_tile_req, status=500) + assert "Cache-Control" not in resp.headers + # no assert_no_cache(resp): returns XML exception that bypasses cache control setting + assert resp.content_type == "text/plain" + assert b"500" in resp.body - with mock_httpd(('localhost', 42423), expected_req): - resp = self.app.get(self.common_tile_req.replace('tilesource', 'tilesource_catchall')) + def test_tile_catchall_error_no_image_response(self, app): + expected_req = [ + ( + {"path": "/foo/1/1/0.png"}, + { + "body": b"error", + "status": 200, + "headers": {"content-type": "text/plain"}, + }, + ) + ] + + with mock_httpd(("localhost", 42423), expected_req): + resp = app.get( + self.common_tile_req.replace("tilesource", "tilesource_catchall") + ) assert_no_cache(resp) - eq_(resp.content_type, 'image/png') + assert resp.content_type == "image/png" img = img_from_buf(resp.body) - eq_(img.getcolors(), [(256 * 256, (100, 50, 50))]) + assert img.getcolors() == [(256 * 256, (100, 50, 50))] def assert_no_cache(resp): - eq_(resp.headers['Pragma'], 'no-cache') - eq_(resp.headers['Expires'], '-1') - eq_(resp.cache_control.no_store, True) + assert resp.headers["Pragma"] == "no-cache" + assert resp.headers["Expires"] == "-1" + assert resp.cache_control.no_store == True diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_tilesource_minmax_res.py mapproxy-1.12.0/mapproxy/test/system/test_tilesource_minmax_res.py --- mapproxy-1.11.0/mapproxy/test/system/test_tilesource_minmax_res.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_tilesource_minmax_res.py 2019-08-30 07:34:08.000000000 +0000 @@ -13,37 +13,42 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pytest + from mapproxy.test.image import tmp_image from mapproxy.test.http import mock_httpd -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config -from nose.tools import eq_ - -test_config = {} -base_config = make_base_config(test_config) +from mapproxy.test.system import SysTest -def setup_module(): - module_setup(test_config, 'tilesource_minmax_res.yaml') -def teardown_module(): - module_teardown(test_config) - -class TestTileSourceMinMaxRes(SystemTest): - config = test_config - - def test_get_tile_res_a(self): - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/tiles_a/06/000/000/000/000/000/001.png'}, - {'body': img.read(), 'headers': {'content-type': 'image/png'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - resp = self.app.get('/tiles/tms_cache/6/0/1.png') - eq_(resp.content_type, 'image/png') - self.created_tiles.append('tms_cache_EPSG900913/06/000/000/000/000/000/001.png') - - def test_get_tile_res_b(self): - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/tiles_b/07/000/000/000/000/000/001.png'}, - {'body': img.read(), 'headers': {'content-type': 'image/png'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - resp = self.app.get('/tiles/tms_cache/7/0/1.png') - eq_(resp.content_type, 'image/png') - self.created_tiles.append('tms_cache_EPSG900913/07/000/000/000/000/000/001.png') +@pytest.fixture(scope="module") +def config_file(): + return "tilesource_minmax_res.yaml" + + +class TestTileSourceMinMaxRes(SysTest): + + def test_get_tile_res_a(self, app, cache_dir): + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + {"path": r"/tiles_a/06/000/000/000/000/000/001.png"}, + {"body": img.read(), "headers": {"content-type": "image/png"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + resp = app.get("/tiles/tms_cache/6/0/1.png") + assert resp.content_type == "image/png" + assert cache_dir.join( + "tms_cache_EPSG900913/06/000/000/000/000/000/001.png" + ).check() + + def test_get_tile_res_b(self, app, cache_dir): + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + {"path": r"/tiles_b/07/000/000/000/000/000/001.png"}, + {"body": img.read(), "headers": {"content-type": "image/png"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + resp = app.get("/tiles/tms_cache/7/0/1.png") + assert resp.content_type == "image/png" + assert cache_dir.join( + "tms_cache_EPSG900913/07/000/000/000/000/000/001.png" + ).check() diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_tms_origin.py mapproxy-1.12.0/mapproxy/test/system/test_tms_origin.py --- mapproxy-1.11.0/mapproxy/test/system/test_tms_origin.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_tms_origin.py 2019-08-30 07:34:08.000000000 +0000 @@ -1,51 +1,46 @@ # This file is part of the MapProxy project. # Copyright (C) 2010-2012 Omniscale -# +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import pytest + from mapproxy.test.image import is_jpeg -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config -from nose.tools import eq_ +from mapproxy.test.system import SysTest -test_config = {} -base_config = make_base_config(test_config) -def setup_module(): - module_setup(test_config, 'tileservice_origin.yaml', with_cache_data=True) +@pytest.fixture(scope="module") +def config_file(): + return "tileservice_origin.yaml" -def teardown_module(): - module_teardown(test_config) -class TestTileServicesOrigin(SystemTest): - config = test_config +@pytest.mark.usefixtures("fixture_cache_data") +class TestTileServicesOrigin(SysTest): ### # tile 0/0/1 is cached, check if we can access it with different URLs - def test_get_cached_tile_tms(self): - resp = self.app.get('/tms/1.0.0/wms_cache/0/0/1.jpeg') - eq_(resp.content_type, 'image/jpeg') - assert is_jpeg(resp.body) - - def test_get_cached_tile_service_origin(self): - resp = self.app.get('/tiles/wms_cache/1/0/0.jpeg') - eq_(resp.content_type, 'image/jpeg') + def test_get_cached_tile_tms(self, app): + resp = app.get("/tms/1.0.0/wms_cache/0/0/1.jpeg") + assert resp.content_type == "image/jpeg" assert is_jpeg(resp.body) - def test_get_cached_tile_request_origin(self): - resp = self.app.get('/tiles/wms_cache/1/0/1.jpeg?origin=sw') - eq_(resp.content_type, 'image/jpeg') + def test_get_cached_tile_service_origin(self, app): + resp = app.get("/tiles/wms_cache/1/0/0.jpeg") + assert resp.content_type == "image/jpeg" assert is_jpeg(resp.body) - - + def test_get_cached_tile_request_origin(self, app): + resp = app.get("/tiles/wms_cache/1/0/1.jpeg?origin=sw") + assert resp.content_type == "image/jpeg" + assert is_jpeg(resp.body) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_tms.py mapproxy-1.12.0/mapproxy/test/system/test_tms.py --- mapproxy-1.11.0/mapproxy/test/system/test_tms.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_tms.py 2019-08-30 07:34:08.000000000 +0000 @@ -15,232 +15,262 @@ import os import hashlib + from io import BytesIO + +import pytest + from mapproxy.compat.image import Image from mapproxy.test.image import is_jpeg, tmp_image from mapproxy.test.http import mock_httpd -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config -from nose.tools import eq_ +from mapproxy.test.system import SysTest -test_config = {} -base_config = make_base_config(test_config) -def setup_module(): - module_setup(test_config, 'layer.yaml', with_cache_data=True) +@pytest.fixture(scope="module") +def config_file(): + return "layer.yaml" -def teardown_module(): - module_teardown(test_config) -class TestTMS(SystemTest): - config = test_config +class TestTMS(SysTest): - def test_tms_capabilities(self): - resp = self.app.get('/tms/1.0.0/') - assert 'WMS Cache Layer' in resp - assert 'WMS Cache Multi Layer' in resp - assert 'TMS Cache Layer' in resp - assert 'TMS Cache Layer + FI' in resp + def test_tms_capabilities(self, app): + resp = app.get("/tms/1.0.0/") + assert "WMS Cache Layer" in resp + assert "WMS Cache Multi Layer" in resp + assert "TMS Cache Layer" in resp + assert "TMS Cache Layer + FI" in resp xml = resp.lxml - assert xml.xpath('count(//TileMap)') == 11 + assert xml.xpath("count(//TileMap)") == 11 # without trailing space - resp2 = self.app.get('/tms/1.0.0') - eq_(resp.body, resp2.body) + resp2 = app.get("/tms/1.0.0") + assert resp.body == resp2.body - def test_tms_layer_capabilities(self): - resp = self.app.get('/tms/1.0.0/wms_cache') - assert 'WMS Cache Layer' in resp + def test_tms_layer_capabilities(self, app): + resp = app.get("/tms/1.0.0/wms_cache") + assert "WMS Cache Layer" in resp xml = resp.lxml - eq_(xml.xpath('count(//TileSet)'), 19) + assert xml.xpath("count(//TileSet)") == 19 - def test_tms_root_resource(self): - resp = self.app.get('/tms') - resp2 = self.app.get('/tms/') - assert 'TileMapService' in resp and 'TileMapService' in resp2 + def test_tms_root_resource(self, app): + resp = app.get("/tms") + resp2 = app.get("/tms/") + assert "TileMapService" in resp and "TileMapService" in resp2 xml = resp.lxml - eq_(xml.xpath('//TileMapService/@version'),['1.0.0']) + assert xml.xpath("//TileMapService/@version") == ["1.0.0"] - def test_tms_get_out_of_bounds_tile(self): + def test_tms_get_out_of_bounds_tile(self, app): for coord in [(0, 0, -1), (-1, 0, 0), (0, -1, 0), (4, 2, 1), (1, 3, 0)]: - yield self.check_out_of_bounds, coord - - def check_out_of_bounds(self, coord): - x, y, z = coord - url = '/tms/1.0.0/wms_cache/%d/%d/%d.jpeg' % (z, x, y) - resp = self.app.get(url , status=404) - xml = resp.lxml - assert ('outside the bounding box' - in xml.xpath('/TileMapServerError/Message/text()')[0]) + x, y, z = coord + url = "/tms/1.0.0/wms_cache/%d/%d/%d.jpeg" % (z, x, y) + resp = app.get(url, status=404) + xml = resp.lxml + assert ( + "outside the bounding box" + in xml.xpath("/TileMapServerError/Message/text()")[0] + ) - def test_invalid_layer(self): - resp = self.app.get('/tms/1.0.0/inVAlid/0/0/0.png', status=404) + def test_invalid_layer(self, app): + resp = app.get("/tms/1.0.0/inVAlid/0/0/0.png", status=404) xml = resp.lxml - assert ('unknown layer: inVAlid' - in xml.xpath('/TileMapServerError/Message/text()')[0]) + assert ( + "unknown layer: inVAlid" + in xml.xpath("/TileMapServerError/Message/text()")[0] + ) - def test_invalid_format(self): - resp = self.app.get('/tms/1.0.0/wms_cache/0/0/1.png', status=404) + def test_invalid_format(self, app): + resp = app.get("/tms/1.0.0/wms_cache/0/0/1.png", status=404) xml = resp.lxml - assert ('invalid format' - in xml.xpath('/TileMapServerError/Message/text()')[0]) + assert "invalid format" in xml.xpath("/TileMapServerError/Message/text()")[0] - def test_get_tile_tile_source_error(self): - resp = self.app.get('/tms/1.0.0/wms_cache/0/0/0.jpeg', status=500) + def test_get_tile_tile_source_error(self, app): + resp = app.get("/tms/1.0.0/wms_cache/0/0/0.jpeg", status=500) xml = resp.lxml - assert ('No response from URL' - in xml.xpath('/TileMapServerError/Message/text()')[0]) - - def test_get_cached_tile(self): - resp = self.app.get('/tms/1.0.0/wms_cache/0/0/1.jpeg') - eq_(resp.content_type, 'image/jpeg') - eq_(resp.content_length, len(resp.body)) + assert ( + "No response from URL" in xml.xpath("/TileMapServerError/Message/text()")[0] + ) + + def test_get_cached_tile(self, app, fixture_cache_data): + resp = app.get("/tms/1.0.0/wms_cache/0/0/1.jpeg") + assert resp.content_type == "image/jpeg" + assert resp.content_length == len(resp.body) data = BytesIO(resp.body) assert is_jpeg(data) - def test_get_tile(self): - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg' - '&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=' - '&VERSION=1.1.1&BBOX=-20037508.3428,-20037508.3428,0.0,0.0' - '&WIDTH=256'}, - {'body': img.read(), 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - resp = self.app.get('/tms/1.0.0/wms_cache/0/0/0.jpeg') - eq_(resp.content_type, 'image/jpeg') - self.created_tiles.append('wms_cache_EPSG900913/01/000/000/000/000/000/000.jpeg') - - def test_get_tile_from_cache_with_tile_source(self): - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/tiles/01/000/000/000/000/000/001.png'}, - {'body': img.read(), 'headers': {'content-type': 'image/png'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - resp = self.app.get('/tms/1.0.0/tms_cache/0/0/1.png') - eq_(resp.content_type, 'image/png') - self.created_tiles.append('tms_cache_EPSG900913/01/000/000/000/000/000/001.png') - - def test_get_tile_with_watermark_cache(self): - with tmp_image((256, 256), format='png', color=(0, 0, 0)) as img: - expected_req = ({'path': r'/tiles/01/000/000/000/000/000/000.png'}, - {'body': img.read(), 'headers': {'content-type': 'image/png'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - resp = self.app.get('/tms/1.0.0/watermark_cache/0/0/0.png') - eq_(resp.content_type, 'image/png') + def test_get_tile(self, app, cache_dir): + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg" + "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=" + "&VERSION=1.1.1&BBOX=-20037508.3428,-20037508.3428,0.0,0.0" + "&WIDTH=256" + }, + {"body": img.read(), "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + resp = app.get("/tms/1.0.0/wms_cache/0/0/0.jpeg") + assert resp.content_type == "image/jpeg" + assert cache_dir.join( + "wms_cache_EPSG900913/01/000/000/000/000/000/000.jpeg" + ).check() + + def test_get_tile_from_cache_with_tile_source(self, app, cache_dir): + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + {"path": r"/tiles/01/000/000/000/000/000/001.png"}, + {"body": img.read(), "headers": {"content-type": "image/png"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + resp = app.get("/tms/1.0.0/tms_cache/0/0/1.png") + assert resp.content_type == "image/png" + assert cache_dir.join( + "tms_cache_EPSG900913/01/000/000/000/000/000/001.png" + ).check() + + def test_get_tile_with_watermark_cache(self, app): + with tmp_image((256, 256), format="png", color=(0, 0, 0)) as img: + expected_req = ( + {"path": r"/tiles/01/000/000/000/000/000/000.png"}, + {"body": img.read(), "headers": {"content-type": "image/png"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + resp = app.get("/tms/1.0.0/watermark_cache/0/0/0.png") + assert resp.content_type == "image/png" img = Image.open(BytesIO(resp.body)) colors = img.getcolors() assert len(colors) >= 2 - eq_(sorted(colors)[-1][1], (0, 0, 0)) + assert sorted(colors)[-1][1] == (0, 0, 0) -class TestTileService(SystemTest): - config = test_config - def test_get_out_of_bounds_tile(self): - for coord in [(0, 0, -1), (-1, 0, 0), (0, -1, 0), (4, 2, 1), (1, 3, 0)]: - yield self.check_out_of_bounds, coord +class TestTileService(SysTest): - def check_out_of_bounds(self, coord): - x, y, z = coord - url = '/tiles/wms_cache/%d/%d/%d.jpeg' % (z, x, y) - resp = self.app.get(url , status=404) - assert 'outside the bounding box' in resp - - def test_invalid_layer(self): - resp = self.app.get('/tiles/inVAlid/0/0/0.png', status=404) - eq_(resp.content_type, 'text/plain') - assert 'unknown layer: inVAlid' in resp - - def test_invalid_format(self): - resp = self.app.get('/tiles/wms_cache/0/0/1.png', status=404) - eq_(resp.content_type, 'text/plain') - assert 'invalid format' in resp - - def test_get_tile_tile_source_error(self): - resp = self.app.get('/tiles/wms_cache/0/0/0.jpeg', status=500) - eq_(resp.content_type, 'text/plain') - assert 'No response from URL' in resp + def test_get_out_of_bounds_tile(self, app): + for coord in [(0, 0, -1), (-1, 0, 0), (0, -1, 0), (4, 2, 1), (1, 3, 0)]: + x, y, z = coord + url = "/tiles/wms_cache/%d/%d/%d.jpeg" % (z, x, y) + resp = app.get(url, status=404) + assert "outside the bounding box" in resp + + def test_invalid_layer(self, app): + resp = app.get("/tiles/inVAlid/0/0/0.png", status=404) + assert resp.content_type == "text/plain" + assert "unknown layer: inVAlid" in resp + + def test_invalid_format(self, app): + resp = app.get("/tiles/wms_cache/0/0/1.png", status=404) + assert resp.content_type == "text/plain" + assert "invalid format" in resp + + def test_get_tile_tile_source_error(self, app): + resp = app.get("/tiles/wms_cache/0/0/0.jpeg", status=500) + assert resp.content_type == "text/plain" + assert "No response from URL" in resp def _check_tile_resp(self, resp): - eq_(resp.content_type, 'image/jpeg') - eq_(resp.content_length, len(resp.body)) + assert resp.content_type == "image/jpeg" + assert resp.content_length == len(resp.body) data = BytesIO(resp.body) assert is_jpeg(data) - def _update_timestamp(self): + def _update_timestamp(self, cache_dir, base_config): timestamp = 1234567890.0 size = 10214 - base_dir = base_config().cache.base_dir - os.utime(os.path.join(base_dir, - 'wms_cache_EPSG900913/01/000/000/000/000/000/001.jpeg'), - (timestamp, timestamp)) - max_age = base_config().tiles.expires_hours * 60 * 60 - etag = hashlib.md5((str(timestamp) + str(size)).encode('ascii')).hexdigest() + base_dir = base_config.cache.base_dir + os.utime( + os.path.join( + base_dir, "wms_cache_EPSG900913/01/000/000/000/000/000/001.jpeg" + ), + (timestamp, timestamp), + ) + max_age = base_config.tiles.expires_hours * 60 * 60 + etag = hashlib.md5((str(timestamp) + str(size)).encode("ascii")).hexdigest() return etag, max_age def _check_cache_control_headers(self, resp, etag, max_age): - eq_(resp.headers['ETag'], etag) - eq_(resp.headers['Last-modified'], 'Fri, 13 Feb 2009 23:31:30 GMT') - eq_(resp.headers['Cache-control'], 'public, max-age=%d, s-maxage=%d' % (max_age, max_age)) - - def test_get_cached_tile(self): - etag, max_age = self._update_timestamp() - resp = self.app.get('/tiles/wms_cache/1/0/1.jpeg') + assert resp.headers["ETag"] == etag + assert resp.headers["Last-modified"] == "Fri, 13 Feb 2009 23:31:30 GMT" + assert resp.headers["Cache-control"] == "public, max-age=%d, s-maxage=%d" % ( + max_age, + max_age, + ) + + def test_get_cached_tile(self, app, cache_dir, base_config, fixture_cache_data): + etag, max_age = self._update_timestamp(cache_dir, base_config) + resp = app.get("/tiles/wms_cache/1/0/1.jpeg") self._check_cache_control_headers(resp, etag, max_age) self._check_tile_resp(resp) - def test_get_cached_tile_flipped_y(self): - etag, max_age = self._update_timestamp() - resp = self.app.get('/tiles/wms_cache/1/0/0.jpeg?origin=nw') + def test_get_cached_tile_flipped_y( + self, app, cache_dir, base_config, fixture_cache_data + ): + etag, max_age = self._update_timestamp(cache_dir, base_config) + resp = app.get("/tiles/wms_cache/1/0/0.jpeg?origin=nw") self._check_cache_control_headers(resp, etag, max_age) self._check_tile_resp(resp) - def test_if_none_match(self): - etag, max_age = self._update_timestamp() - resp = self.app.get('/tiles/wms_cache/1/0/1.jpeg', - headers={'If-None-Match': etag}) - eq_(resp.status, '304 Not Modified') + def test_if_none_match(self, app, cache_dir, base_config, fixture_cache_data): + etag, max_age = self._update_timestamp(cache_dir, base_config) + resp = app.get("/tiles/wms_cache/1/0/1.jpeg", headers={"If-None-Match": etag}) + assert resp.status == "304 Not Modified" self._check_cache_control_headers(resp, etag, max_age) - resp = self.app.get('/tiles/wms_cache/1/0/1.jpeg', - headers={'If-None-Match': etag + 'foo'}) + resp = app.get( + "/tiles/wms_cache/1/0/1.jpeg", headers={"If-None-Match": etag + "foo"} + ) self._check_cache_control_headers(resp, etag, max_age) - eq_(resp.status, '200 OK') + assert resp.status == "200 OK" self._check_tile_resp(resp) - def test_if_modified_since(self): - etag, max_age = self._update_timestamp() - for date, modified in ( - ('Fri, 15 Feb 2009 23:31:30 GMT', False), - ('Fri, 13 Feb 2009 23:31:31 GMT', False), - ('Fri, 13 Feb 2009 23:31:30 GMT', False), - ('Fri, 13 Feb 2009 23:31:29 GMT', True), - ('Fri, 11 Feb 2009 23:31:29 GMT', True), - ('Friday, 13-Feb-09 23:31:30 GMT', False), - ('Friday, 13-Feb-09 23:31:29 GMT', True), - ('Fri Feb 13 23:31:30 2009', False), - ('Fri Feb 13 23:31:29 2009', True), - # and some invalid ones - ('Fri Foo 13 23:31:29 2009', True), - ('1234567890', True), - ): - yield self.check_modified_response, date, modified, etag, max_age - - def check_modified_response(self, date, modified, etag, max_age): - resp = self.app.get('/tiles/wms_cache/1/0/1.jpeg', headers={ - 'If-Modified-Since': date}) + @pytest.mark.parametrize( + "date,modified", + [ + ("Fri, 15 Feb 2009 23:31:30 GMT", False), + ("Fri, 13 Feb 2009 23:31:31 GMT", False), + ("Fri, 13 Feb 2009 23:31:30 GMT", False), + ("Fri, 13 Feb 2009 23:31:29 GMT", True), + ("Fri, 11 Feb 2009 23:31:29 GMT", True), + ("Friday, 13-Feb-09 23:31:30 GMT", False), + ("Friday, 13-Feb-09 23:31:29 GMT", True), + ("Fri Feb 13 23:31:30 2009", False), + ("Fri Feb 13 23:31:29 2009", True), + # and some invalid ones + ("Fri Foo 13 23:31:29 2009", True), + ("1234567890", True), + ], + ) + def test_if_modified_since( + self, app, cache_dir, base_config, fixture_cache_data, date, modified + ): + etag, max_age = self._update_timestamp(cache_dir, base_config) + resp = app.get( + "/tiles/wms_cache/1/0/1.jpeg", headers={"If-Modified-Since": date} + ) self._check_cache_control_headers(resp, etag, max_age) if modified: - eq_(resp.status, '200 OK') + assert resp.status == "200 OK" self._check_tile_resp(resp) else: - eq_(resp.status, '304 Not Modified') + assert resp.status == "304 Not Modified" - def test_get_tile(self): - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg' - '&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=' - '&VERSION=1.1.1&BBOX=-20037508.3428,-20037508.3428,0.0,0.0' - '&WIDTH=256'}, - {'body': img.read(), 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - resp = self.app.get('/tiles/wms_cache/1/0/0.jpeg') - eq_(resp.content_type, 'image/jpeg') - self.created_tiles.append('wms_cache_EPSG900913/01/000/000/000/000/000/000.jpeg') + def test_get_tile(self, app, cache_dir): + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg" + "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=" + "&VERSION=1.1.1&BBOX=-20037508.3428,-20037508.3428,0.0,0.0" + "&WIDTH=256" + }, + {"body": img.read(), "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + resp = app.get("/tiles/wms_cache/1/0/0.jpeg") + assert resp.content_type == "image/jpeg" + assert cache_dir.join( + "wms_cache_EPSG900913/01/000/000/000/000/000/000.jpeg" + ).check() diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_util_conf.py mapproxy-1.12.0/mapproxy/test/system/test_util_conf.py --- mapproxy-1.11.0/mapproxy/test/system/test_util_conf.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_util_conf.py 2019-08-30 07:34:08.000000000 +0000 @@ -24,17 +24,13 @@ from mapproxy.script.conf.app import config_command from mapproxy.test.helper import capture -from nose.tools import eq_ - def filename(name): - return os.path.join( - os.path.dirname(__file__), - 'fixture', - name, - ) + return os.path.join(os.path.dirname(__file__), "fixture", name) + class TestMapProxyConfCmd(object): + def setup(self): self.dir = tempfile.mkdtemp() @@ -43,183 +39,266 @@ shutil.rmtree(self.dir) def tmp_filename(self, name): - return os.path.join( - self.dir, - name, - ) + return os.path.join(self.dir, name) def test_cmd_no_args(self): with capture() as (stdout, stderr): - assert config_command(['mapproxy-conf']) == 2 + assert config_command(["mapproxy-conf"]) == 2 - assert '--capabilities required' in stderr.getvalue() + assert "--capabilities required" in stderr.getvalue() def test_stdout_output(self): with capture(bytes=True) as (stdout, stderr): - assert config_command(['mapproxy-conf', '--capabilities', filename('util-conf-wms-111-cap.xml')]) == 0 + assert ( + config_command( + [ + "mapproxy-conf", + "--capabilities", + filename("util-conf-wms-111-cap.xml"), + ] + ) + == 0 + ) - assert stdout.getvalue().startswith(b'# MapProxy configuration') + assert stdout.getvalue().startswith(b"# MapProxy configuration") def test_test_cap_output_no_base(self): with capture(bytes=True) as (stdout, stderr): - assert config_command(['mapproxy-conf', - '--capabilities', filename('util-conf-wms-111-cap.xml'), - '--output', self.tmp_filename('mapproxy.yaml'), - ]) == 0 - - - with open(self.tmp_filename('mapproxy.yaml'), 'rb') as f: - conf = yaml.load(f) - - assert 'grids' not in conf - eq_(conf['sources'], { - 'osm_roads_wms': { - 'supported_srs': ['CRS:84', 'EPSG:25831', 'EPSG:25832', 'EPSG:25833', 'EPSG:31466', 'EPSG:31467', 'EPSG:31468', 'EPSG:3857', 'EPSG:4258', 'EPSG:4326', 'EPSG:900913'], - 'req': {'layers': 'osm_roads', 'url': 'http://osm.omniscale.net/proxy/service?', 'transparent': True}, - 'type': 'wms', - 'coverage': {'srs': 'EPSG:4326', 'bbox': [-180.0, -85.0511287798, 180.0, 85.0511287798]} - }, - 'osm_wms': { - 'supported_srs': ['CRS:84', 'EPSG:25831', 'EPSG:25832', 'EPSG:25833', 'EPSG:31466', 'EPSG:31467', 'EPSG:31468', 'EPSG:3857', 'EPSG:4258', 'EPSG:4326', 'EPSG:900913'], - 'req': {'layers': 'osm', 'url': 'http://osm.omniscale.net/proxy/service?', 'transparent': True}, - 'type': 'wms', - 'coverage': { - 'srs': 'EPSG:4326', - 'bbox': [-180.0, -85.0511287798, 180.0, 85.0511287798], - }, - }, - }) - - eq_(conf['layers'], [{ - 'title': 'Omniscale OpenStreetMap WMS', - 'layers': [ - { - 'name': 'osm', - 'title': 'OpenStreetMap (complete map)', - 'sources': ['osm_wms'], - }, - { - 'name': 'osm_roads', - 'title': 'OpenStreetMap (streets only)', - 'sources': ['osm_roads_wms'], - }, - ] - }]) - eq_(len(conf['layers'][0]['layers']), 2) + assert ( + config_command( + [ + "mapproxy-conf", + "--capabilities", + filename("util-conf-wms-111-cap.xml"), + "--output", + self.tmp_filename("mapproxy.yaml"), + ] + ) + == 0 + ) + + with open(self.tmp_filename("mapproxy.yaml"), "rb") as f: + conf = yaml.safe_load(f) + + assert "grids" not in conf + assert conf["sources"] == { + "osm_roads_wms": { + "supported_srs": [ + "CRS:84", + "EPSG:25831", + "EPSG:25832", + "EPSG:25833", + "EPSG:31466", + "EPSG:31467", + "EPSG:31468", + "EPSG:3857", + "EPSG:4258", + "EPSG:4326", + "EPSG:900913", + ], + "req": { + "layers": "osm_roads", + "url": "http://osm.omniscale.net/proxy/service?", + "transparent": True, + }, + "type": "wms", + "coverage": { + "srs": "EPSG:4326", + "bbox": [-180.0, -85.0511287798, 180.0, 85.0511287798], + }, + }, + "osm_wms": { + "supported_srs": [ + "CRS:84", + "EPSG:25831", + "EPSG:25832", + "EPSG:25833", + "EPSG:31466", + "EPSG:31467", + "EPSG:31468", + "EPSG:3857", + "EPSG:4258", + "EPSG:4326", + "EPSG:900913", + ], + "req": { + "layers": "osm", + "url": "http://osm.omniscale.net/proxy/service?", + "transparent": True, + }, + "type": "wms", + "coverage": { + "srs": "EPSG:4326", + "bbox": [-180.0, -85.0511287798, 180.0, 85.0511287798], + }, + }, + } + + assert conf["layers"] == [ + { + "title": "Omniscale OpenStreetMap WMS", + "layers": [ + { + "name": "osm", + "title": "OpenStreetMap (complete map)", + "sources": ["osm_wms"], + }, + { + "name": "osm_roads", + "title": "OpenStreetMap (streets only)", + "sources": ["osm_roads_wms"], + }, + ], + } + ] + assert len(conf["layers"][0]["layers"]) == 2 def test_test_cap_output(self): with capture(bytes=True) as (stdout, stderr): - assert config_command(['mapproxy-conf', - '--capabilities', filename('util-conf-wms-111-cap.xml'), - '--output', self.tmp_filename('mapproxy.yaml'), - '--base', filename('util-conf-base-grids.yaml'), - ]) == 0 - - - with open(self.tmp_filename('mapproxy.yaml'), 'rb') as f: - conf = yaml.load(f) - - assert 'grids' not in conf - eq_(len(conf['sources']), 2) - - eq_(conf['caches'], { - 'osm_cache': { - 'grids': ['webmercator', 'geodetic'], - 'sources': ['osm_wms'] - }, - 'osm_roads_cache': { - 'grids': ['webmercator', 'geodetic'], - 'sources': ['osm_roads_wms'] - }, - }) - - - eq_(conf['layers'], [{ - 'title': 'Omniscale OpenStreetMap WMS', - 'layers': [ - { - 'name': 'osm', - 'title': 'OpenStreetMap (complete map)', - 'sources': ['osm_cache'], - }, - { - 'name': 'osm_roads', - 'title': 'OpenStreetMap (streets only)', - 'sources': ['osm_roads_cache'], - }, - ] - }]) - eq_(len(conf['layers'][0]['layers']), 2) + assert ( + config_command( + [ + "mapproxy-conf", + "--capabilities", + filename("util-conf-wms-111-cap.xml"), + "--output", + self.tmp_filename("mapproxy.yaml"), + "--base", + filename("util-conf-base-grids.yaml"), + ] + ) + == 0 + ) + + with open(self.tmp_filename("mapproxy.yaml"), "rb") as f: + conf = yaml.safe_load(f) + + assert "grids" not in conf + assert len(conf["sources"]) == 2 + + assert conf["caches"] == { + "osm_cache": { + "grids": ["webmercator", "geodetic"], + "sources": ["osm_wms"], + }, + "osm_roads_cache": { + "grids": ["webmercator", "geodetic"], + "sources": ["osm_roads_wms"], + }, + } + + assert conf["layers"] == [ + { + "title": "Omniscale OpenStreetMap WMS", + "layers": [ + { + "name": "osm", + "title": "OpenStreetMap (complete map)", + "sources": ["osm_cache"], + }, + { + "name": "osm_roads", + "title": "OpenStreetMap (streets only)", + "sources": ["osm_roads_cache"], + }, + ], + } + ] + assert len(conf["layers"][0]["layers"]) == 2 def test_overwrites(self): with capture(bytes=True) as (stdout, stderr): - assert config_command(['mapproxy-conf', - '--capabilities', filename('util-conf-wms-111-cap.xml'), - '--output', self.tmp_filename('mapproxy.yaml'), - '--overwrite', filename('util-conf-overwrite.yaml'), - '--base', filename('util-conf-base-grids.yaml'), - ]) == 0 - - - with open(self.tmp_filename('mapproxy.yaml'), 'rb') as f: - conf = yaml.load(f) - - assert 'grids' not in conf - eq_(len(conf['sources']), 2) - - eq_(conf['sources'], { - 'osm_roads_wms': { - 'supported_srs': ['EPSG:3857'], - 'req': {'layers': 'osm_roads', 'url': 'http://osm.omniscale.net/proxy/service?', 'transparent': True, 'param': 42}, - 'type': 'wms', - 'coverage': {'srs': 'EPSG:4326', 'bbox': [0, 0, 90, 90]} - }, - 'osm_wms': { - 'supported_srs': ['CRS:84', 'EPSG:25831', 'EPSG:25832', 'EPSG:25833', 'EPSG:31466', 'EPSG:31467', 'EPSG:31468', 'EPSG:3857', 'EPSG:4258', 'EPSG:4326', 'EPSG:900913'], - 'req': {'layers': 'osm', 'url': 'http://osm.omniscale.net/proxy/service?', 'transparent': True, 'param': 42}, - 'type': 'wms', - 'coverage': { - 'srs': 'EPSG:4326', - 'bbox': [-180.0, -85.0511287798, 180.0, 85.0511287798], - }, - }, - }) - - - eq_(conf['caches'], { - 'osm_cache': { - 'grids': ['webmercator', 'geodetic'], - 'sources': ['osm_wms'], - 'cache': { - 'type': 'sqlite' - }, - }, - 'osm_roads_cache': { - 'grids': ['webmercator'], - 'sources': ['osm_roads_wms'], - 'cache': { - 'type': 'sqlite' - }, - }, - }) - - - eq_(conf['layers'], [{ - 'title': 'Omniscale OpenStreetMap WMS', - 'layers': [ - { - 'name': 'osm', - 'title': 'OpenStreetMap (complete map)', - 'sources': ['osm_cache'], - }, - { - 'name': 'osm_roads', - 'title': 'OpenStreetMap (streets only)', - 'sources': ['osm_roads_cache'], - }, - ] - }]) - eq_(len(conf['layers'][0]['layers']), 2) - + assert ( + config_command( + [ + "mapproxy-conf", + "--capabilities", + filename("util-conf-wms-111-cap.xml"), + "--output", + self.tmp_filename("mapproxy.yaml"), + "--overwrite", + filename("util-conf-overwrite.yaml"), + "--base", + filename("util-conf-base-grids.yaml"), + ] + ) + == 0 + ) + + with open(self.tmp_filename("mapproxy.yaml"), "rb") as f: + conf = yaml.safe_load(f) + + assert "grids" not in conf + assert len(conf["sources"]) == 2 + + assert conf["sources"] == { + "osm_roads_wms": { + "supported_srs": ["EPSG:3857"], + "req": { + "layers": "osm_roads", + "url": "http://osm.omniscale.net/proxy/service?", + "transparent": True, + "param": 42, + }, + "type": "wms", + "coverage": {"srs": "EPSG:4326", "bbox": [0, 0, 90, 90]}, + }, + "osm_wms": { + "supported_srs": [ + "CRS:84", + "EPSG:25831", + "EPSG:25832", + "EPSG:25833", + "EPSG:31466", + "EPSG:31467", + "EPSG:31468", + "EPSG:3857", + "EPSG:4258", + "EPSG:4326", + "EPSG:900913", + ], + "req": { + "layers": "osm", + "url": "http://osm.omniscale.net/proxy/service?", + "transparent": True, + "param": 42, + }, + "type": "wms", + "coverage": { + "srs": "EPSG:4326", + "bbox": [-180.0, -85.0511287798, 180.0, 85.0511287798], + }, + }, + } + assert conf["caches"] == { + "osm_cache": { + "grids": ["webmercator", "geodetic"], + "sources": ["osm_wms"], + "cache": {"type": "sqlite"}, + }, + "osm_roads_cache": { + "grids": ["webmercator"], + "sources": ["osm_roads_wms"], + "cache": {"type": "sqlite"}, + }, + } + assert conf["layers"] == [ + { + "title": "Omniscale OpenStreetMap WMS", + "layers": [ + { + "name": "osm", + "title": "OpenStreetMap (complete map)", + "sources": ["osm_cache"], + }, + { + "name": "osm_roads", + "title": "OpenStreetMap (streets only)", + "sources": ["osm_roads_cache"], + }, + ], + } + ] + assert len(conf["layers"][0]["layers"]) == 2 diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_util_export.py mapproxy-1.12.0/mapproxy/test/system/test_util_export.py --- mapproxy-1.11.0/mapproxy/test/system/test_util_export.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_util_export.py 2019-08-30 07:34:08.000000000 +0000 @@ -18,114 +18,193 @@ import shutil import contextlib -from nose.tools import eq_, assert_raises +import pytest + from mapproxy.script.export import export_command from mapproxy.test.image import tmp_image from mapproxy.test.http import mock_httpd from mapproxy.test.helper import capture -FIXTURE_DIR = os.path.join(os.path.dirname(__file__), 'fixture') + +FIXTURE_DIR = os.path.join(os.path.dirname(__file__), "fixture") + @contextlib.contextmanager def tile_server(tile_coords): - with tmp_image((256, 256), format='jpeg') as img: + with tmp_image((256, 256), format="jpeg") as img: img = img.read() expected_reqs = [] for tile in tile_coords: expected_reqs.append( - ({'path': r'/tiles/%d/%d/%d.png' % (tile[2], tile[0], tile[1])}, - {'body': img, 'headers': {'content-type': 'image/png'}})) - with mock_httpd(('localhost', 42423), expected_reqs, unordered=True): + ( + {"path": r"/tiles/%d/%d/%d.png" % (tile[2], tile[0], tile[1])}, + {"body": img, "headers": {"content-type": "image/png"}}, + ) + ) + with mock_httpd(("localhost", 42423), expected_reqs, unordered=True): yield + class TestUtilExport(object): + def setup(self): self.dir = tempfile.mkdtemp() - self.dest = os.path.join(self.dir, 'dest') - self.mapproxy_conf_name = 'mapproxy_export.yaml' + self.dest = os.path.join(self.dir, "dest") + self.mapproxy_conf_name = "mapproxy_export.yaml" shutil.copy(os.path.join(FIXTURE_DIR, self.mapproxy_conf_name), self.dir) self.mapproxy_conf_file = os.path.join(self.dir, self.mapproxy_conf_name) - self.args = ['command_dummy', '-f', self.mapproxy_conf_file] + self.args = ["command_dummy", "-f", self.mapproxy_conf_file] def teardown(self): shutil.rmtree(self.dir) def test_config_not_found(self): - self.args = ['command_dummy', '-f', 'foo.bar'] + self.args = ["command_dummy", "-f", "foo.bar"] with capture() as (out, err): try: export_command(self.args) except SystemExit as ex: assert ex.code != 0 else: - assert False, 'export command did not exit' + assert False, "export command did not exit" assert err.getvalue().startswith("ERROR:") def test_no_fetch_missing_tiles(self): - self.args += ['--grid', 'GLOBAL_MERCATOR', '--dest', self.dest, - '--levels', '0', '--source', 'tms_cache'] + self.args += [ + "--grid", + "GLOBAL_MERCATOR", + "--dest", + self.dest, + "--levels", + "0", + "--source", + "tms_cache", + ] with capture() as (out, err): export_command(self.args) - eq_(os.listdir(self.dest), ['tile_locks']) + assert os.listdir(self.dest) == ["tile_locks"] def test_fetch_missing_tiles(self): - self.args += ['--grid', 'GLOBAL_MERCATOR', '--dest', self.dest, - '--levels', '0,1', '--source', 'tms_cache', '--fetch-missing-tiles'] + self.args += [ + "--grid", + "GLOBAL_MERCATOR", + "--dest", + self.dest, + "--levels", + "0,1", + "--source", + "tms_cache", + "--fetch-missing-tiles", + ] with tile_server([(0, 0, 0), (0, 0, 1), (0, 1, 1), (1, 0, 1), (1, 1, 1)]): with capture() as (out, err): export_command(self.args) - assert os.path.exists(os.path.join(self.dest, 'tile_locks')) - assert os.path.exists(os.path.join(self.dest, '0', '0', '0.png')) - assert os.path.exists(os.path.join(self.dest, '1', '0', '0.png')) - assert os.path.exists(os.path.join(self.dest, '1', '0', '1.png')) - assert os.path.exists(os.path.join(self.dest, '1', '1', '0.png')) - assert os.path.exists(os.path.join(self.dest, '1', '1', '1.png')) + assert os.path.exists(os.path.join(self.dest, "tile_locks")) + assert os.path.exists(os.path.join(self.dest, "0", "0", "0.png")) + assert os.path.exists(os.path.join(self.dest, "1", "0", "0.png")) + assert os.path.exists(os.path.join(self.dest, "1", "0", "1.png")) + assert os.path.exists(os.path.join(self.dest, "1", "1", "0.png")) + assert os.path.exists(os.path.join(self.dest, "1", "1", "1.png")) def test_force(self): - self.args += ['--grid', 'GLOBAL_MERCATOR', '--dest', self.dest, - '--levels', '0', '--source', 'tms_cache'] + self.args += [ + "--grid", + "GLOBAL_MERCATOR", + "--dest", + self.dest, + "--levels", + "0", + "--source", + "tms_cache", + ] with capture() as (out, err): export_command(self.args) with capture() as (out, err): - assert_raises(SystemExit, export_command, self.args) + with pytest.raises(SystemExit): + export_command(self.args) with capture() as (out, err): - export_command(self.args + ['--force']) + export_command(self.args + ["--force"]) def test_invalid_grid_definition(self): - self.args += ['--grid', 'foo=1', '--dest', self.dest, - '--levels', '0', '--source', 'tms_cache'] + self.args += [ + "--grid", + "foo=1", + "--dest", + self.dest, + "--levels", + "0", + "--source", + "tms_cache", + ] with capture() as (out, err): - assert_raises(SystemExit, export_command, self.args) - assert 'foo' in err.getvalue() + with pytest.raises(SystemExit): + export_command(self.args) + assert "foo" in err.getvalue() def test_custom_grid(self): - self.args += ['--grid', 'base=GLOBAL_MERCATOR min_res=100000', '--dest', self.dest, - '--levels', '1', '--source', 'tms_cache', '--fetch-missing-tiles'] - with tile_server([(0, 3, 2), (1, 3, 2), (2, 3, 2), (3, 3, 2), - (0, 2, 2), (1, 2, 2), (2, 2, 2), (3, 2, 2), - (0, 1, 2), (1, 1, 2), (2, 1, 2), (3, 1, 2), - (0, 0, 2), (1, 0, 2), (2, 0, 2), (3, 0, 2)]): + self.args += [ + "--grid", + "base=GLOBAL_MERCATOR min_res=100000", + "--dest", + self.dest, + "--levels", + "1", + "--source", + "tms_cache", + "--fetch-missing-tiles", + ] + with tile_server( + [ + (0, 3, 2), + (1, 3, 2), + (2, 3, 2), + (3, 3, 2), + (0, 2, 2), + (1, 2, 2), + (2, 2, 2), + (3, 2, 2), + (0, 1, 2), + (1, 1, 2), + (2, 1, 2), + (3, 1, 2), + (0, 0, 2), + (1, 0, 2), + (2, 0, 2), + (3, 0, 2), + ] + ): with capture() as (out, err): export_command(self.args) - assert os.path.exists(os.path.join(self.dest, 'tile_locks')) - assert os.path.exists(os.path.join(self.dest, '1', '0', '0.png')) - assert os.path.exists(os.path.join(self.dest, '1', '3', '3.png')) - + assert os.path.exists(os.path.join(self.dest, "tile_locks")) + assert os.path.exists(os.path.join(self.dest, "1", "0", "0.png")) + assert os.path.exists(os.path.join(self.dest, "1", "3", "3.png")) def test_coverage(self): - self.args += ['--grid', 'GLOBAL_MERCATOR', '--dest', self.dest, - '--levels', '0..2', '--source', 'tms_cache', '--fetch-missing-tiles', - '--coverage', '10,10,20,20', '--srs', 'EPSG:4326'] + self.args += [ + "--grid", + "GLOBAL_MERCATOR", + "--dest", + self.dest, + "--levels", + "0..2", + "--source", + "tms_cache", + "--fetch-missing-tiles", + "--coverage", + "10,10,20,20", + "--srs", + "EPSG:4326", + ] with tile_server([(0, 0, 0), (1, 1, 1), (2, 2, 2)]): with capture() as (out, err): export_command(self.args) - assert os.path.exists(os.path.join(self.dest, 'tile_locks')) - assert os.path.exists(os.path.join(self.dest, '0', '0', '0.png')) - assert os.path.exists(os.path.join(self.dest, '1', '1', '1.png')) - assert os.path.exists(os.path.join(self.dest, '2', '2', '2.png')) + assert os.path.exists(os.path.join(self.dest, "tile_locks")) + assert os.path.exists(os.path.join(self.dest, "0", "0", "0.png")) + assert os.path.exists(os.path.join(self.dest, "1", "1", "1.png")) + assert os.path.exists(os.path.join(self.dest, "2", "2", "2.png")) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_util_grids.py mapproxy-1.12.0/mapproxy/test/system/test_util_grids.py --- mapproxy-1.11.0/mapproxy/test/system/test_util_grids.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_util_grids.py 2019-08-30 07:34:08.000000000 +0000 @@ -15,70 +15,66 @@ import os -from nose.tools import assert_raises +import pytest + from mapproxy.script.grids import grids_command from mapproxy.test.helper import capture -FIXTURE_DIR = os.path.join(os.path.dirname(__file__), 'fixture') -GRID_NAMES = [ - 'global_geodetic_sqrt2', - 'grid_full_example', - 'another_grid_full_example' -] -UNUSED_GRID_NAMES = [ - 'GLOBAL_GEODETIC', - 'GLOBAL_MERCATOR', - 'GLOBAL_WEBMERCATOR', -] + +FIXTURE_DIR = os.path.join(os.path.dirname(__file__), "fixture") +GRID_NAMES = ["global_geodetic_sqrt2", "grid_full_example", "another_grid_full_example"] +UNUSED_GRID_NAMES = ["GLOBAL_GEODETIC", "GLOBAL_MERCATOR", "GLOBAL_WEBMERCATOR"] class TestUtilGrids(object): + def setup(self): - self.mapproxy_config_file = os.path.join(FIXTURE_DIR, 'util_grids.yaml') - self.args = ['command_dummy', '-f', self.mapproxy_config_file] + self.mapproxy_config_file = os.path.join(FIXTURE_DIR, "util_grids.yaml") + self.args = ["command_dummy", "-f", self.mapproxy_config_file] def test_config_not_found(self): - self.args = ['command_dummy', '-f', 'foo.bar'] + self.args = ["command_dummy", "-f", "foo.bar"] with capture() as (_, err): - assert_raises(SystemExit, grids_command, self.args) + with pytest.raises(SystemExit): + grids_command(self.args) assert err.getvalue().startswith("ERROR:") def test_list_configured(self): - self.args.append('-l') + self.args.append("-l") with capture() as (out, err): grids_command(self.args) captured_output = out.getvalue() for grid in GRID_NAMES: assert grid in captured_output - number_of_lines = sum(1 for line in captured_output.split('\n') if line) + number_of_lines = sum(1 for line in captured_output.split("\n") if line) assert number_of_lines == len(GRID_NAMES) def test_list_configured_all(self): - self.args.append('-l') - self.args.append('--all') + self.args.append("-l") + self.args.append("--all") with capture() as (out, err): grids_command(self.args) captured_output = out.getvalue() for grid in GRID_NAMES + UNUSED_GRID_NAMES: assert grid in captured_output - number_of_lines = sum(1 for line in captured_output.split('\n') if line) + number_of_lines = sum(1 for line in captured_output.split("\n") if line) assert number_of_lines == len(UNUSED_GRID_NAMES) + len(GRID_NAMES) def test_display_single_grid(self): - self.args.append('-g') - self.args.append('GLOBAL_MERCATOR') + self.args.append("-g") + self.args.append("GLOBAL_MERCATOR") with capture() as (out, err): grids_command(self.args) captured_output = out.getvalue() assert "GLOBAL_MERCATOR" in captured_output def test_ignore_case(self): - self.args.append('-g') - self.args.append('global_geodetic') + self.args.append("-g") + self.args.append("global_geodetic") with capture() as (out, err): grids_command(self.args) captured_output = out.getvalue() @@ -90,5 +86,3 @@ captured_output = out.getvalue() assert "GLOBAL_MERCATOR" in captured_output assert "origin*: 'll'" in captured_output - - diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_util_wms_capabilities.py mapproxy-1.12.0/mapproxy/test/system/test_util_wms_capabilities.py --- mapproxy-1.11.0/mapproxy/test/system/test_util_wms_capabilities.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_util_wms_capabilities.py 2019-08-30 07:34:08.000000000 +0000 @@ -15,86 +15,168 @@ import os -from nose.tools import assert_raises +import pytest from mapproxy.client.http import HTTPClient from mapproxy.script.wms_capabilities import wms_capabilities_command from mapproxy.test.http import mock_httpd from mapproxy.test.helper import capture -TESTSERVER_ADDRESS = ('127.0.0.1', 56413) -TESTSERVER_URL = 'http://%s:%s' % TESTSERVER_ADDRESS -CAPABILITIES111_FILE = os.path.join(os.path.dirname(__file__), 'fixture', 'util_wms_capabilities111.xml') -CAPABILITIES130_FILE = os.path.join(os.path.dirname(__file__), 'fixture', 'util_wms_capabilities130.xml') -SERVICE_EXCEPTION_FILE = os.path.join(os.path.dirname(__file__), 'fixture', 'util_wms_capabilities_service_exception.xml') + +TESTSERVER_ADDRESS = ("127.0.0.1", 56413) +TESTSERVER_URL = "http://%s:%s" % TESTSERVER_ADDRESS +CAPABILITIES111_FILE = os.path.join( + os.path.dirname(__file__), "fixture", "util_wms_capabilities111.xml" +) +CAPABILITIES130_FILE = os.path.join( + os.path.dirname(__file__), "fixture", "util_wms_capabilities130.xml" +) +SERVICE_EXCEPTION_FILE = os.path.join( + os.path.dirname(__file__), "fixture", "util_wms_capabilities_service_exception.xml" +) class TestUtilWMSCapabilities(object): + def setup(self): self.client = HTTPClient() - self.args = ['command_dummy', '--host', TESTSERVER_URL + '/service'] + self.args = ["command_dummy", "--host", TESTSERVER_URL + "/service"] def test_http_error(self): - self.args = ['command_dummy', '--host', 'http://foo.doesnotexist'] - with capture() as (out,err): - assert_raises(SystemExit, wms_capabilities_command, self.args) + self.args = ["command_dummy", "--host", "http://foo.doesnotexist"] + with capture() as (out, err): + with pytest.raises(SystemExit): + wms_capabilities_command(self.args) assert err.getvalue().startswith("ERROR:") - self.args[2] = '/no/valid/url' - with capture() as (out,err): - assert_raises(SystemExit, wms_capabilities_command, self.args) + self.args[2] = "/no/valid/url" + with capture() as (out, err): + with pytest.raises(SystemExit): + wms_capabilities_command(self.args) assert err.getvalue().startswith("ERROR:") def test_request_not_parsable(self): - with mock_httpd(TESTSERVER_ADDRESS, [({'path': '/service?request=GetCapabilities&version=1.1.1&service=WMS', 'method': 'GET'}, - {'status': '200', 'body': ''})]): - with capture() as (out,err): - assert_raises(SystemExit, wms_capabilities_command, self.args) - error_msg = err.getvalue().rsplit('-'*80, 1)[1].strip() - assert error_msg.startswith('Could not parse the document') + with mock_httpd( + TESTSERVER_ADDRESS, + [ + ( + { + "path": "/service?request=GetCapabilities&version=1.1.1&service=WMS", + "method": "GET", + }, + {"status": "200", "body": ""}, + ) + ], + ): + with capture() as (out, err): + with pytest.raises(SystemExit): + wms_capabilities_command(self.args) + error_msg = err.getvalue().rsplit("-" * 80, 1)[1].strip() + assert error_msg.startswith("Could not parse the document") def test_service_exception(self): - self.args = ['command_dummy', '--host', TESTSERVER_URL + '/service?request=GetCapabilities'] - with open(SERVICE_EXCEPTION_FILE, 'rb') as fp: + self.args = [ + "command_dummy", + "--host", + TESTSERVER_URL + "/service?request=GetCapabilities", + ] + with open(SERVICE_EXCEPTION_FILE, "rb") as fp: capabilities_doc = fp.read() - with mock_httpd(TESTSERVER_ADDRESS, [({'path': '/service?request=GetCapabilities&version=1.1.1&service=WMS', 'method': 'GET'}, - {'status': '200', 'body': capabilities_doc})]): - with capture() as (out,err): - assert_raises(SystemExit, wms_capabilities_command, self.args) - error_msg = err.getvalue().rsplit('-'*80, 1)[1].strip() - assert 'Not a capabilities document' in error_msg + with mock_httpd( + TESTSERVER_ADDRESS, + [ + ( + { + "path": "/service?request=GetCapabilities&version=1.1.1&service=WMS", + "method": "GET", + }, + {"status": "200", "body": capabilities_doc}, + ) + ], + ): + with capture() as (out, err): + with pytest.raises(SystemExit): + wms_capabilities_command(self.args) + error_msg = err.getvalue().rsplit("-" * 80, 1)[1].strip() + assert "Not a capabilities document" in error_msg def test_parse_capabilities(self): - self.args = ['command_dummy', '--host', TESTSERVER_URL + '/service?request=GetCapabilities', '--version', '1.1.1'] - with open(CAPABILITIES111_FILE, 'rb') as fp: + self.args = [ + "command_dummy", + "--host", + TESTSERVER_URL + "/service?request=GetCapabilities", + "--version", + "1.1.1", + ] + with open(CAPABILITIES111_FILE, "rb") as fp: capabilities_doc = fp.read() - with mock_httpd(TESTSERVER_ADDRESS, [({'path': '/service?request=GetCapabilities&version=1.1.1&service=WMS', 'method': 'GET'}, - {'status': '200', 'body': capabilities_doc})]): - with capture() as (out,err): + with mock_httpd( + TESTSERVER_ADDRESS, + [ + ( + { + "path": "/service?request=GetCapabilities&version=1.1.1&service=WMS", + "method": "GET", + }, + {"status": "200", "body": capabilities_doc}, + ) + ], + ): + with capture() as (out, err): wms_capabilities_command(self.args) - lines = out.getvalue().split('\n') - assert lines[1].startswith('Capabilities Document Version 1.1.1') + lines = out.getvalue().split("\n") + assert lines[1].startswith("Capabilities Document Version 1.1.1") def test_parse_130capabilities(self): - self.args = ['command_dummy', '--host', TESTSERVER_URL + '/service?request=GetCapabilities', '--version', '1.3.0'] - with open(CAPABILITIES130_FILE, 'rb') as fp: + self.args = [ + "command_dummy", + "--host", + TESTSERVER_URL + "/service?request=GetCapabilities", + "--version", + "1.3.0", + ] + with open(CAPABILITIES130_FILE, "rb") as fp: capabilities_doc = fp.read() - with mock_httpd(TESTSERVER_ADDRESS, [({'path': '/service?request=GetCapabilities&version=1.3.0&service=WMS', 'method': 'GET'}, - {'status': '200', 'body': capabilities_doc})]): - with capture() as (out,err): + with mock_httpd( + TESTSERVER_ADDRESS, + [ + ( + { + "path": "/service?request=GetCapabilities&version=1.3.0&service=WMS", + "method": "GET", + }, + {"status": "200", "body": capabilities_doc}, + ) + ], + ): + with capture() as (out, err): wms_capabilities_command(self.args) - lines = out.getvalue().split('\n') - assert lines[1].startswith('Capabilities Document Version 1.3.0') + lines = out.getvalue().split("\n") + assert lines[1].startswith("Capabilities Document Version 1.3.0") def test_key_error(self): - self.args = ['command_dummy', '--host', TESTSERVER_URL + '/service?request=GetCapabilities'] - with open(CAPABILITIES111_FILE, 'rb') as fp: + self.args = [ + "command_dummy", + "--host", + TESTSERVER_URL + "/service?request=GetCapabilities", + ] + with open(CAPABILITIES111_FILE, "rb") as fp: capabilities_doc = fp.read() - capabilities_doc = capabilities_doc.replace(b'minx', b'foo') - with mock_httpd(TESTSERVER_ADDRESS, [({'path': '/service?request=GetCapabilities&version=1.1.1&service=WMS', 'method': 'GET'}, - {'status': '200', 'body': capabilities_doc})]): - with capture() as (out,err): - assert_raises(SystemExit, wms_capabilities_command, self.args) - - assert err.getvalue().startswith('XML-Element has no such attribute') + capabilities_doc = capabilities_doc.replace(b"minx", b"foo") + with mock_httpd( + TESTSERVER_ADDRESS, + [ + ( + { + "path": "/service?request=GetCapabilities&version=1.1.1&service=WMS", + "method": "GET", + }, + {"status": "200", "body": capabilities_doc}, + ) + ], + ): + with capture() as (out, err): + with pytest.raises(SystemExit): + wms_capabilities_command(self.args) + assert err.getvalue().startswith("XML-Element has no such attribute") diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_watermark.py mapproxy-1.12.0/mapproxy/test/system/test_watermark.py --- mapproxy-1.11.0/mapproxy/test/system/test_watermark.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_watermark.py 2019-08-30 07:34:08.000000000 +0000 @@ -17,58 +17,75 @@ from io import BytesIO +import pytest + from mapproxy.compat.image import Image from mapproxy.request.wms import WMS111MapRequest from mapproxy.test.http import mock_httpd from mapproxy.test.image import tmp_image -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config +from mapproxy.test.system import SysTest -from nose.tools import eq_ -test_config = {} -base_config = make_base_config(test_config) +@pytest.fixture(scope="module") +def config_file(): + return "watermark.yaml" -def setup_module(): - module_setup(test_config, 'watermark.yaml', with_cache_data=True) -def teardown_module(): - module_teardown(test_config) +class TestWatermark(SysTest): -class WatermarkTest(SystemTest): - config = test_config def setup(self): - SystemTest.setup(self) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='-180,0,0,80', width='200', height='200', - layers='watermark', srs='EPSG:4326', format='image/png', - styles='', request='GetMap')) - - def test_watermark_tile(self): - with tmp_image((256, 256), format='png', color=(0, 0, 0)) as img: - expected_req = ({'path': r'/service?LAYERs=blank&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=-180.0,-90.0,0.0,90.0' - '&WIDTH=256'}, - {'body': img.read(), 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - resp = self.app.get('/tms/1.0.0/watermark/EPSG4326/0/0/0.png') - eq_(resp.content_type, 'image/png') + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="-180,0,0,80", + width="200", + height="200", + layers="watermark", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + ), + ) + + def test_watermark_tile(self, app): + with tmp_image((256, 256), format="png", color=(0, 0, 0)) as img: + expected_req = ( + { + "path": r"/service?LAYERs=blank&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=-180.0,-90.0,0.0,90.0" + "&WIDTH=256" + }, + {"body": img.read(), "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + resp = app.get("/tms/1.0.0/watermark/EPSG4326/0/0/0.png") + assert resp.content_type == "image/png" img = Image.open(BytesIO(resp.body)) colors = img.getcolors() assert len(colors) >= 2 - eq_(sorted(colors)[-1][1], (0, 0, 0)) + assert sorted(colors)[-1][1] == (0, 0, 0) - def test_transparent_watermark_tile(self): - with tmp_image((256, 256), format='png', color=(0, 0, 0, 0), mode='RGBA') as img: - expected_req = ({'path': r'/service?LAYERs=blank&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=-180.0,-90.0,0.0,90.0' - '&WIDTH=256'}, - {'body': img.read(), 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - resp = self.app.get('/tms/1.0.0/watermark_transp/EPSG4326/0/0/0.png') - eq_(resp.content_type, 'image/png') + def test_transparent_watermark_tile(self, app): + with tmp_image( + (256, 256), format="png", color=(0, 0, 0, 0), mode="RGBA" + ) as img: + expected_req = ( + { + "path": r"/service?LAYERs=blank&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=-180.0,-90.0,0.0,90.0" + "&WIDTH=256" + }, + {"body": img.read(), "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + resp = app.get("/tms/1.0.0/watermark_transp/EPSG4326/0/0/0.png") + assert resp.content_type == "image/png" img = Image.open(BytesIO(resp.body)) colors = img.getcolors() assert len(colors) >= 2 - eq_(sorted(colors)[-1][1], (0, 0, 0, 0)) + assert sorted(colors)[-1][1] == (0, 0, 0, 0) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_wmsc.py mapproxy-1.12.0/mapproxy/test/system/test_wmsc.py --- mapproxy-1.11.0/mapproxy/test/system/test_wmsc.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_wmsc.py 2019-08-30 07:34:08.000000000 +0000 @@ -16,77 +16,100 @@ from __future__ import division from io import BytesIO + +import pytest + from mapproxy.request.wms import ( - WMS111MapRequest, WMS111FeatureInfoRequest, WMS111CapabilitiesRequest + WMS111MapRequest, + WMS111FeatureInfoRequest, + WMS111CapabilitiesRequest, ) from mapproxy.test.image import is_jpeg from mapproxy.test.helper import validate_with_dtd from mapproxy.test.system.test_wms import is_111_exception -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config -from nose.tools import eq_ +from mapproxy.test.system import SysTest + -test_config = {} -base_config = make_base_config(test_config) +@pytest.fixture(scope="module") +def config_file(): + return "layer.yaml" -def setup_module(): - module_setup(test_config, 'layer.yaml', with_cache_data=True) -def teardown_module(): - module_teardown(test_config) +class TestWMSC(SysTest): -class TestWMSC(SystemTest): - config = test_config def setup(self): - SystemTest.setup(self) - self.common_cap_req = WMS111CapabilitiesRequest(url='/service?', param=dict(service='WMS', - version='1.1.1')) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='-20037508,0.0,0.0,20037508', width='256', height='256', - layers='wms_cache', srs='EPSG:900913', format='image/jpeg', - styles='', request='GetMap')) - self.common_fi_req = WMS111FeatureInfoRequest(url='/service?', - param=dict(x='10', y='20', width='200', height='200', layers='wms_cache', - format='image/png', query_layers='wms_cache', styles='', - bbox='1000,400,2000,1400', srs='EPSG:900913')) - - def test_capabilities(self): - req = str(self.common_cap_req) + '&tiled=true' - resp = self.app.get(req) + self.common_cap_req = WMS111CapabilitiesRequest( + url="/service?", param=dict(service="WMS", version="1.1.1") + ) + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="-20037508,0.0,0.0,20037508", + width="256", + height="256", + layers="wms_cache", + srs="EPSG:900913", + format="image/jpeg", + styles="", + request="GetMap", + ), + ) + self.common_fi_req = WMS111FeatureInfoRequest( + url="/service?", + param=dict( + x="10", + y="20", + width="200", + height="200", + layers="wms_cache", + format="image/png", + query_layers="wms_cache", + styles="", + bbox="1000,400,2000,1400", + srs="EPSG:900913", + ), + ) + + def test_capabilities(self, app): + req = str(self.common_cap_req) + "&tiled=true" + resp = app.get(req) xml = resp.lxml - assert validate_with_dtd(xml, dtd_name='wmsc/1.1.1/WMS_MS_Capabilities.dtd') - srs = set([e.text for e in xml.xpath('//TileSet/SRS')]) - eq_(srs, set(['EPSG:4326', 'EPSG:900913'])) - eq_(len(xml.xpath('//TileSet')), 11) - - def test_get_tile(self): - resp = self.app.get(str(self.common_map_req) + '&tiled=true') - assert 'public' in resp.headers['Cache-Control'] - eq_(resp.content_type, 'image/jpeg') + assert validate_with_dtd(xml, dtd_name="wmsc/1.1.1/WMS_MS_Capabilities.dtd") + srs = set([e.text for e in xml.xpath("//TileSet/SRS")]) + assert srs == set(["EPSG:4326", "EPSG:900913"]) + assert len(xml.xpath("//TileSet")) == 11 + + def test_get_tile(self, app, fixture_cache_data): + resp = app.get(str(self.common_map_req) + "&tiled=true") + assert "public" in resp.headers["Cache-Control"] + assert resp.content_type == "image/jpeg" data = BytesIO(resp.body) assert is_jpeg(data) - def test_get_tile_w_rounded_bbox(self): - self.common_map_req.params.bbox = '-20037400,0.0,0.0,20037400' - resp = self.app.get(str(self.common_map_req) + '&tiled=true') - assert 'public' in resp.headers['Cache-Control'] - eq_(resp.content_type, 'image/jpeg') + def test_get_tile_w_rounded_bbox(self, app, fixture_cache_data): + self.common_map_req.params.bbox = "-20037400,0.0,0.0,20037400" + resp = app.get(str(self.common_map_req) + "&tiled=true") + assert "public" in resp.headers["Cache-Control"] + assert resp.content_type == "image/jpeg" data = BytesIO(resp.body) assert is_jpeg(data) - def test_get_tile_wrong_bbox(self): - self.common_map_req.params.bbox = '-20037508,0.0,200000.0,20037508' - resp = self.app.get(str(self.common_map_req) + '&tiled=true') - assert 'Cache-Control' not in resp.headers - is_111_exception(resp.lxml, re_msg='.*invalid bbox') - - def test_get_tile_wrong_fromat(self): - self.common_map_req.params.format = 'image/png' - resp = self.app.get(str(self.common_map_req) + '&tiled=true') - assert 'Cache-Control' not in resp.headers - is_111_exception(resp.lxml, re_msg='Invalid request: invalid.*format.*jpeg') + def test_get_tile_wrong_bbox(self, app): + self.common_map_req.params.bbox = "-20037508,0.0,200000.0,20037508" + resp = app.get(str(self.common_map_req) + "&tiled=true") + assert "Cache-Control" not in resp.headers + is_111_exception(resp.lxml, re_msg=".*invalid bbox") + + def test_get_tile_wrong_fromat(self, app): + self.common_map_req.params.format = "image/png" + resp = app.get(str(self.common_map_req) + "&tiled=true") + assert "Cache-Control" not in resp.headers + is_111_exception(resp.lxml, re_msg="Invalid request: invalid.*format.*jpeg") - def test_get_tile_wrong_size(self): + def test_get_tile_wrong_size(self, app): self.common_map_req.params.size = (256, 255) - resp = self.app.get(str(self.common_map_req) + '&tiled=true') - assert 'Cache-Control' not in resp.headers - is_111_exception(resp.lxml, re_msg='Invalid request: invalid.*size.*256x256') + resp = app.get(str(self.common_map_req) + "&tiled=true") + assert "Cache-Control" not in resp.headers + is_111_exception(resp.lxml, re_msg="Invalid request: invalid.*size.*256x256") diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_wms.py mapproxy-1.12.0/mapproxy/test/system/test_wms.py --- mapproxy-1.11.0/mapproxy/test/system/test_wms.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_wms.py 2019-08-30 07:34:08.000000000 +0000 @@ -15,1087 +15,1558 @@ from __future__ import print_function, division -import os import re import sys -import shutil import functools + from io import BytesIO +import pytest + +from mapproxy.image import ImageSource from mapproxy.srs import SRS from mapproxy.compat.image import Image -from mapproxy.request.wms import WMS100MapRequest, WMS111MapRequest, WMS130MapRequest, \ - WMS111FeatureInfoRequest, WMS111CapabilitiesRequest, \ - WMS130CapabilitiesRequest, WMS100CapabilitiesRequest, \ - WMS100FeatureInfoRequest, WMS130FeatureInfoRequest, \ - WMS110MapRequest, WMS110FeatureInfoRequest, \ - WMS110CapabilitiesRequest, \ - wms_request +from mapproxy.request.wms import ( + WMS100MapRequest, + WMS111MapRequest, + WMS130MapRequest, + WMS111FeatureInfoRequest, + WMS111CapabilitiesRequest, + WMS130CapabilitiesRequest, + WMS100CapabilitiesRequest, + WMS100FeatureInfoRequest, + WMS130FeatureInfoRequest, + WMS110MapRequest, + WMS110FeatureInfoRequest, + WMS110CapabilitiesRequest, + wms_request, +) from mapproxy.test.image import is_jpeg, is_png, tmp_image, create_tmp_image +from mapproxy.test.unit.test_image import assert_geotiff_tags from mapproxy.test.http import mock_httpd from mapproxy.test.helper import validate_with_dtd, validate_with_xsd -from mapproxy.test.unit.test_grid import assert_almost_equal_bbox -from nose.tools import eq_, assert_almost_equal - -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config +from mapproxy.test.system import SysTest -test_config = {} -base_config = make_base_config(test_config) -def setup_module(): - module_setup(test_config, 'layer.yaml', with_cache_data=True) +@pytest.fixture(scope="module") +def config_file(): + return "layer.yaml" -def teardown_module(): - module_teardown(test_config) -def test_invalid_url(): - test_config['app'].get('/invalid?fop', status=404) +class TestBase(SysTest): -def is_100_capa(xml): - return validate_with_dtd(xml, dtd_name='wms/1.0.0/capabilities_1_0_0.dtd') + def test_invalid_url(self, app): + app.get("/invalid?fop", status=404) -def is_110_capa(xml): - return validate_with_dtd(xml, dtd_name='wms/1.1.0/capabilities_1_1_0.dtd') -def is_111_exception(xml, msg=None, code=None, re_msg=None): - eq_(xml.xpath('/ServiceExceptionReport/@version')[0], '1.1.1') - if msg: - eq_(xml.xpath('//ServiceException/text()')[0], msg) - if re_msg: - exception_msg = xml.xpath('//ServiceException/text()')[0] - assert re.findall(re_msg, exception_msg, re.I), "'%r' does not match '%s'" % ( - re_msg, exception_msg) - if code is not None: - eq_(xml.xpath('/ServiceExceptionReport/ServiceException/@code')[0], code) - assert validate_with_dtd(xml, 'wms/1.1.1/exception_1_1_1.dtd') +class TestCoverageWMS(SysTest): + config_file = "layer.yaml" -def is_111_capa(xml): - return validate_with_dtd(xml, dtd_name='wms/1.1.1/WMS_MS_Capabilities.dtd') -def is_130_capa(xml): - return validate_with_xsd(xml, xsd_name='wms/1.3.0/capabilities_1_3_0.xsd') - - -class WMSTest(SystemTest): - config = test_config - -class TestCoverageWMS(WMSTest): - - def test_unknown_version_110(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' - '&VERSION=1.1.0') + def test_unknown_version_110(self, app): + resp = app.get( + "http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities" + "&VERSION=1.1.0" + ) assert is_110_capa(resp.lxml) - def test_unknown_version_113(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' - '&VERSION=1.1.3') + + def test_unknown_version_113(self, app): + resp = app.get( + "http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities" + "&VERSION=1.1.3" + ) assert is_111_capa(resp.lxml) - def test_unknown_version_090(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' - '&WMTVER=0.9.0') + + def test_unknown_version_090(self, app): + resp = app.get( + "http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities" + "&WMTVER=0.9.0" + ) assert is_100_capa(resp.lxml) - def test_unknown_version_200(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' - '&VERSION=2.0.0') + + def test_unknown_version_200(self, app): + resp = app.get( + "http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities" + "&VERSION=2.0.0" + ) assert is_130_capa(resp.lxml) + def bbox_srs_from_boundingbox(bbox_elem): return [ - float(bbox_elem.attrib['minx']), - float(bbox_elem.attrib['miny']), - float(bbox_elem.attrib['maxx']), - float(bbox_elem.attrib['maxy']), + float(bbox_elem.attrib["minx"]), + float(bbox_elem.attrib["miny"]), + float(bbox_elem.attrib["maxx"]), + float(bbox_elem.attrib["maxy"]), ] -class TestWMS111(WMSTest): + +class TestWMS111(SysTest): + config_file = "layer.yaml" + def setup(self): - WMSTest.setup(self) - self.common_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1')) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='-180,0,0,80', width='200', height='200', - layers='wms_cache', srs='EPSG:4326', format='image/png', - styles='', request='GetMap')) - self.common_fi_req = WMS111FeatureInfoRequest(url='/service?', - param=dict(x='10', y='20', width='200', height='200', layers='wms_cache', - format='image/png', query_layers='wms_cache', styles='', - bbox='1000,400,2000,1400', srs='EPSG:900913')) - - def test_invalid_request_type(self): - req = str(self.common_map_req).replace('GetMap', 'invalid') - resp = self.app.get(req) + # WMSTest.setup(self) + self.common_req = WMS111MapRequest( + url="/service?", param=dict(service="WMS", version="1.1.1") + ) + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="-180,0,0,80", + width="200", + height="200", + layers="wms_cache", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + ), + ) + self.common_fi_req = WMS111FeatureInfoRequest( + url="/service?", + param=dict( + x="10", + y="20", + width="200", + height="200", + layers="wms_cache", + format="image/png", + query_layers="wms_cache", + styles="", + bbox="1000,400,2000,1400", + srs="EPSG:900913", + ), + ) + + def test_invalid_request_type(self, app): + req = str(self.common_map_req).replace("GetMap", "invalid") + resp = app.get(req) is_111_exception(resp.lxml, "unknown WMS request type 'invalid'") - def test_endpoints(self): - for endpoint in ('service', 'ows', 'wms'): - req = WMS111CapabilitiesRequest(url='/%s?' % endpoint).copy_with_request_params(self.common_req) - resp = self.app.get(req) - eq_(resp.content_type, 'application/vnd.ogc.wms_xml') + def test_endpoints(self, app): + for endpoint in ("service", "ows", "wms"): + req = WMS111CapabilitiesRequest( + url="/%s?" % endpoint + ).copy_with_request_params(self.common_req) + resp = app.get(req) + assert resp.content_type == "application/vnd.ogc.wms_xml" xml = resp.lxml - assert validate_with_dtd(xml, dtd_name='wms/1.1.1/WMS_MS_Capabilities.dtd') + assert validate_with_dtd(xml, dtd_name="wms/1.1.1/WMS_MS_Capabilities.dtd") - def test_wms_capabilities(self): - req = WMS111CapabilitiesRequest(url='/service?').copy_with_request_params(self.common_req) - resp = self.app.get(req) - eq_(resp.content_type, 'application/vnd.ogc.wms_xml') - xml = resp.lxml - eq_(xml.xpath('//GetMap//OnlineResource/@xlink:href', - namespaces=dict(xlink="http://www.w3.org/1999/xlink"))[0], - 'http://localhost/service?') + def test_wms_capabilities(self, app): + req = WMS111CapabilitiesRequest(url="/service?").copy_with_request_params( + self.common_req + ) + resp = app.get(req) + assert resp.content_type == "application/vnd.ogc.wms_xml" + xml = resp.lxml + assert ( + xml.xpath( + "//GetMap//OnlineResource/@xlink:href", + namespaces=dict(xlink="http://www.w3.org/1999/xlink"), + )[0] == + "http://localhost/service?" + ) # test for MetadataURL - eq_(xml.xpath('//Layer/MetadataURL/OnlineResource/@xlink:href', - namespaces=dict(xlink="http://www.w3.org/1999/xlink"))[0], - 'http://some.url/') - eq_(xml.xpath('//Layer/MetadataURL/@type')[0], - 'TC211') - - layer_names = set(xml.xpath('//Layer/Layer/Name/text()')) - expected_names = set(['direct_fwd_params', 'direct', 'wms_cache', - 'wms_cache_100', 'wms_cache_130', 'wms_cache_transparent', - 'wms_merge', 'tms_cache', 'tms_fi_cache', 'wms_cache_multi', - 'wms_cache_link_single', 'wms_cache_110', 'watermark_cache']) - eq_(layer_names, expected_names) - eq_(set(xml.xpath('//Layer/Layer[3]/Abstract/text()')), - set(['Some abstract'])) + assert ( + xml.xpath( + "//Layer/MetadataURL/OnlineResource/@xlink:href", + namespaces=dict(xlink="http://www.w3.org/1999/xlink"), + )[0] == + "http://some.url/" + ) + assert xml.xpath("//Layer/MetadataURL/@type")[0] == "TC211" + + layer_names = set(xml.xpath("//Layer/Layer/Name/text()")) + expected_names = set( + [ + "direct_fwd_params", + "direct", + "wms_cache", + "wms_cache_100", + "wms_cache_130", + "wms_cache_transparent", + "wms_merge", + "tms_cache", + "tms_fi_cache", + "wms_cache_multi", + "wms_cache_link_single", + "wms_cache_110", + "watermark_cache", + ] + ) + assert layer_names == expected_names + assert set(xml.xpath("//Layer/Layer[3]/Abstract/text()")) == set(["Some abstract"]) - bboxs = xml.xpath('//Layer/Layer[1]/BoundingBox') - bboxs = dict((e.attrib['SRS'], e) for e in bboxs) + bboxs = xml.xpath("//Layer/Layer[1]/BoundingBox") + bboxs = dict((e.attrib["SRS"], e) for e in bboxs) assert_almost_equal_bbox( - bbox_srs_from_boundingbox(bboxs['EPSG:3857']), - [-20037508.3428, -15538711.0963, 18924313.4349, 15538711.0963]) + bbox_srs_from_boundingbox(bboxs["EPSG:3857"]), + [-20037508.3428, -15538711.0963, 18924313.4349, 15538711.0963], + ) assert_almost_equal_bbox( - bbox_srs_from_boundingbox(bboxs['EPSG:4326']), - [-180.0, -70.0, 170.0, 80.0]) + bbox_srs_from_boundingbox(bboxs["EPSG:4326"]), [-180.0, -70.0, 170.0, 80.0] + ) - bbox_srs = xml.xpath('//Layer/Layer/BoundingBox') - bbox_srs = set(e.attrib['SRS'] for e in bbox_srs) + bbox_srs = xml.xpath("//Layer/Layer/BoundingBox") + bbox_srs = set(e.attrib["SRS"] for e in bbox_srs) # we have a coverage in EPSG:4258, but it is not in wms.srs (#288) - assert 'EPSG:4258' not in bbox_srs + assert "EPSG:4258" not in bbox_srs - assert validate_with_dtd(xml, dtd_name='wms/1.1.1/WMS_MS_Capabilities.dtd') + assert validate_with_dtd(xml, dtd_name="wms/1.1.1/WMS_MS_Capabilities.dtd") - def test_invalid_layer(self): - self.common_map_req.params['layers'] = 'invalid' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - is_111_exception(resp.lxml, 'unknown layer: invalid', 'LayerNotDefined') - - def test_invalid_layer_img_exception(self): - self.common_map_req.params['layers'] = 'invalid' - self.common_map_req.params['exceptions'] = 'application/vnd.ogc.se_inimage' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_invalid_layer(self, app): + self.common_map_req.params["layers"] = "invalid" + resp = app.get(self.common_map_req) + assert resp.content_type == "application/vnd.ogc.se_xml" + is_111_exception(resp.lxml, "unknown layer: invalid", "LayerNotDefined") + + def test_invalid_layer_img_exception(self, app): + self.common_map_req.params["layers"] = "invalid" + self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" assert is_png(BytesIO(resp.body)) - def test_invalid_format(self): - self.common_map_req.params['format'] = 'image/ascii' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - is_111_exception(resp.lxml, 'unsupported image format: image/ascii', - 'InvalidFormat') - - def test_invalid_format_img_exception(self): - self.common_map_req.params['format'] = 'image/ascii' - self.common_map_req.params['exceptions'] = 'application/vnd.ogc.se_inimage' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_invalid_format(self, app): + self.common_map_req.params["format"] = "image/ascii" + resp = app.get(self.common_map_req) + assert resp.content_type == "application/vnd.ogc.se_xml" + is_111_exception( + resp.lxml, "unsupported image format: image/ascii", "InvalidFormat" + ) + + def test_invalid_format_img_exception(self, app): + self.common_map_req.params["format"] = "image/ascii" + self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" assert is_png(BytesIO(resp.body)) - def test_invalid_format_options_img_exception(self): - self.common_map_req.params['format'] = 'image/png; mode=12bit' - self.common_map_req.params['exceptions'] = 'application/vnd.ogc.se_inimage' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_invalid_format_options_img_exception(self, app): + self.common_map_req.params["format"] = "image/png; mode=12bit" + self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" assert is_png(BytesIO(resp.body)) - def test_missing_format_img_exception(self): - del self.common_map_req.params['format'] - self.common_map_req.params['exceptions'] = 'application/vnd.ogc.se_inimage' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_missing_format_img_exception(self, app): + del self.common_map_req.params["format"] + self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" assert is_png(BytesIO(resp.body)) - def test_invalid_srs(self): - self.common_map_req.params['srs'] = 'EPSG:1234' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - is_111_exception(resp.lxml, 'unsupported srs: EPSG:1234', 'InvalidSRS') - - def test_get_map_unknown_style(self): - self.common_map_req.params['styles'] = 'unknown' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - is_111_exception(resp.lxml, 'unsupported styles: unknown', 'StyleNotDefined') + def test_invalid_srs(self, app): + self.common_map_req.params["srs"] = "EPSG:1234" + resp = app.get(self.common_map_req) + assert resp.content_type == "application/vnd.ogc.se_xml" + is_111_exception(resp.lxml, "unsupported srs: EPSG:1234", "InvalidSRS") + + def test_get_map_unknown_style(self, app): + self.common_map_req.params["styles"] = "unknown" + resp = app.get(self.common_map_req) + assert resp.content_type == "application/vnd.ogc.se_xml" + is_111_exception(resp.lxml, "unsupported styles: unknown", "StyleNotDefined") - def test_get_map_too_large(self): + def test_get_map_too_large(self, app): self.common_map_req.params.size = (5000, 5000) - self.common_map_req.params['exceptions'] = 'application/vnd.ogc.se_inimage' - resp = self.app.get(self.common_map_req) + self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage" + resp = app.get(self.common_map_req) # is xml, even if inimage was requested - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - is_111_exception(resp.lxml, 'image size too large') + assert resp.content_type == "application/vnd.ogc.se_xml" + is_111_exception(resp.lxml, "image size too large") + + def test_get_map_default_style(self, app, fixture_cache_data): + self.common_map_req.params["styles"] = "default" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" + data = BytesIO(resp.body) + assert is_png(data) + assert Image.open(data).mode == "RGB" - def test_get_map_default_style(self): - self.common_map_req.params['styles'] = 'default' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_get_map_png(self, app, fixture_cache_data): + resp = app.get(self.common_map_req) + assert "Cache-Control" not in resp.headers + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - assert Image.open(data).mode == 'RGB' + assert Image.open(data).mode == "RGB" - def test_get_map_png(self): - resp = self.app.get(self.common_map_req) - assert 'Cache-Control' not in resp.headers - eq_(resp.content_type, 'image/png') + def test_get_map_float_size(self, app, fixture_cache_data): + self.common_map_req.params['width'] = '200.0' + resp = app.get(self.common_map_req) + assert "Cache-Control" not in resp.headers + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - assert Image.open(data).mode == 'RGB' + assert Image.open(data).mode == "RGB" - def test_get_map_png8_custom_format(self): - self.common_map_req.params['layers'] = 'wms_cache' - self.common_map_req.params['format'] = 'image/png; mode=8bit' - resp = self.app.get(self.common_map_req) - eq_(resp.headers['Content-type'], 'image/png; mode=8bit') + def test_get_map_png8_custom_format(self, app, fixture_cache_data): + self.common_map_req.params["layers"] = "wms_cache" + self.common_map_req.params["format"] = "image/png; mode=8bit" + resp = app.get(self.common_map_req) + assert resp.headers["Content-type"] == "image/png; mode=8bit" data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) - eq_(img.mode, 'P') + assert img.mode == "P" - def test_get_map_png_transparent_non_transparent_data(self): - self.common_map_req.params['transparent'] = 'True' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_get_map_png_transparent_non_transparent_data( + self, app, fixture_cache_data + ): + self.common_map_req.params["transparent"] = "True" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) - eq_(img.mode, 'RGB') + assert img.mode == "RGB" - def test_get_map_png_transparent(self): - self.common_map_req.params['layers'] = 'wms_cache_transparent' - self.common_map_req.params['transparent'] = 'True' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_get_map_png_transparent(self, app, fixture_cache_data): + self.common_map_req.params["layers"] = "wms_cache_transparent" + self.common_map_req.params["transparent"] = "True" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - assert Image.open(data).mode == 'RGBA' + assert Image.open(data).mode == "RGBA" - def test_get_map_png_w_default_bgcolor(self): - self.common_map_req.params['layers'] = 'wms_cache_transparent' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_get_map_png_w_default_bgcolor(self, app, fixture_cache_data): + self.common_map_req.params["layers"] = "wms_cache_transparent" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) - eq_(img.mode, 'RGB') - eq_(img.getcolors()[0][1], (255, 255, 255)) + assert img.mode == "RGB" + assert img.getcolors()[0][1] == (255, 255, 255) - def test_get_map_png_w_bgcolor(self): - self.common_map_req.params['layers'] = 'wms_cache_transparent' - self.common_map_req.params['bgcolor'] = '0xff00a0' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_get_map_png_w_bgcolor(self, app, fixture_cache_data): + self.common_map_req.params["layers"] = "wms_cache_transparent" + self.common_map_req.params["bgcolor"] = "0xff00a0" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) img = Image.open(data) - eq_(img.mode, 'RGB') - eq_(sorted(img.getcolors())[-1][1], (255, 0, 160)) + assert img.mode == "RGB" + assert sorted(img.getcolors())[-1][1] == (255, 0, 160) - def test_get_map_jpeg(self): - self.common_map_req.params['format'] = 'image/jpeg' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/jpeg') + def test_get_map_jpeg(self, app, fixture_cache_data): + self.common_map_req.params["format"] = "image/jpeg" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/jpeg" assert is_jpeg(BytesIO(resp.body)) - def test_get_map_xml_exception(self): - self.common_map_req.params['bbox'] = '0,0,90,90' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - xml = resp.lxml - eq_(xml.xpath('/ServiceExceptionReport/ServiceException/@code'), []) - assert 'No response from URL' in xml.xpath('//ServiceException/text()')[0] - assert validate_with_dtd(xml, 'wms/1.1.1/exception_1_1_1.dtd') - - def test_direct_layer_error(self): - self.common_map_req.params['layers'] = 'direct' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') + def test_get_map_geotiff(self, app, fixture_cache_data): + self.common_map_req.params["format"] = "image/tiff" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/tiff" + img = ImageSource(BytesIO(resp.body)).as_image() + assert_geotiff_tags(img, (-180, 80), (180/200.0, 80/200.0), 4326, False) + + def test_get_map_xml_exception(self, app): + self.common_map_req.params["bbox"] = "0,0,90,90" + resp = app.get(self.common_map_req) + assert resp.content_type == "application/vnd.ogc.se_xml" + xml = resp.lxml + assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == [] + assert "No response from URL" in xml.xpath("//ServiceException/text()")[0] + assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd") + + def test_direct_layer_error(self, app): + self.common_map_req.params["layers"] = "direct" + resp = app.get(self.common_map_req) + assert resp.content_type == "application/vnd.ogc.se_xml" xml = resp.lxml - eq_(xml.xpath('/ServiceExceptionReport/ServiceException/@code'), []) + assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == [] # TODO hide error # assert 'unable to get map for layers: direct' in \ # xml.xpath('//ServiceException/text()')[0] - assert 'No response from URL' in \ - xml.xpath('//ServiceException/text()')[0] + assert "No response from URL" in xml.xpath("//ServiceException/text()")[0] - assert validate_with_dtd(xml, 'wms/1.1.1/exception_1_1_1.dtd') + assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd") - def test_direct_layer_non_image_response(self): - self.common_map_req.params['layers'] = 'direct' - expected_req = ({'path': r'/service?LAYERs=bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=-180.0,0.0,0.0,80.0' - '&WIDTH=200'}, - {'body': b'notanimage', 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') + def test_direct_layer_non_image_response(self, app): + self.common_map_req.params["layers"] = "direct" + expected_req = ( + { + "path": r"/service?LAYERs=bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=-180.0,0.0,0.0,80.0" + "&WIDTH=200" + }, + {"body": b"notanimage", "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + resp = app.get(self.common_map_req) + assert resp.content_type == "application/vnd.ogc.se_xml" xml = resp.lxml - eq_(xml.xpath('/ServiceExceptionReport/ServiceException/@code'), []) - assert 'error while processing image file' in \ - xml.xpath('//ServiceException/text()')[0] - - assert validate_with_dtd(xml, 'wms/1.1.1/exception_1_1_1.dtd') - - def test_get_map(self): - # check custom tile lock directory - tiles_lock_dir = os.path.join(test_config['base_dir'], 'wmscachetilelockdir') - # make sure custom tile_lock_dir was not created by other tests - shutil.rmtree(tiles_lock_dir, ignore_errors=True) - assert not os.path.exists(tiles_lock_dir) - - self.created_tiles.append('wms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg') - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg' - '&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=' - '&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428' - '&WIDTH=256'}, - {'body': img.read(), 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - self.common_map_req.params['bbox'] = '0,0,180,90' - resp = self.app.get(self.common_map_req) - assert 35000 < int(resp.headers['Content-length']) < 75000 - eq_(resp.content_type, 'image/png') + assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == [] + assert ( + "error while processing image file" + in xml.xpath("//ServiceException/text()")[0] + ) + + assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd") + + def test_get_map_non_image_response(self, app, cache_dir): + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg" + "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=" + "&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428" + "&WIDTH=256" + }, + {"body": b"notanimage", "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + self.common_map_req.params["bbox"] = "0,0,180,90" + resp = app.get(self.common_map_req) + assert resp.content_type == "application/vnd.ogc.se_xml" + + xml = resp.lxml + assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == [] + assert ( + "unable to transform image: cannot identify image file" + in xml.xpath("//ServiceException/text()")[0] + ) + + assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd") + + assert cache_dir.join( + "wms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg" + ).check() + + def test_get_map(self, app, base_dir, cache_dir): + # check global tile lock directory + tiles_lock_dir = base_dir.join("wmscachetilelockdir") + # make sure global tile_lock_dir was ot created by other tests + if tiles_lock_dir.check(): + tiles_lock_dir.remove() + + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg" + "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=" + "&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428" + "&WIDTH=256" + }, + {"body": img.read(), "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + self.common_map_req.params["bbox"] = "0,0,180,90" + resp = app.get(self.common_map_req) + assert 35000 < int(resp.headers["Content-length"]) < 75000 + assert resp.content_type == "image/png" # check custom tile_lock_dir - assert os.path.exists(tiles_lock_dir) + assert cache_dir.join( + "wms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg" + ).check() + assert tiles_lock_dir.check() - def test_get_map_non_image_response(self): - self.created_tiles.append('wms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg') - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg' - '&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=' - '&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428' - '&WIDTH=256'}, - {'body': b'notanimage', 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - self.common_map_req.params['bbox'] = '0,0,180,90' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - - xml = resp.lxml - eq_(xml.xpath('/ServiceExceptionReport/ServiceException/@code'), []) - assert 'unable to transform image: cannot identify image file' in \ - xml.xpath('//ServiceException/text()')[0] - - assert validate_with_dtd(xml, 'wms/1.1.1/exception_1_1_1.dtd') - - def test_get_map_direct_fwd_params_layer(self): - img = create_tmp_image((200, 200), format='png') - expected_req = ({'path': r'/service?LAYERs=bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=-180.0,0.0,0.0,80.0' - '&WIDTH=200&TIME=20041012'}, - {'body': img}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - self.common_map_req.params['layers'] = 'direct_fwd_params' - self.common_map_req.params['time'] = '20041012' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') - - def test_get_map_use_direct_from_level(self): - with tmp_image((200, 200), format='png') as img: - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=' - '&VERSION=1.1.1&BBOX=5.0,-10.0,6.0,-9.0' - '&WIDTH=200'}, - {'body': img.read(), 'headers': {'content-type': 'image/png'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - self.common_map_req.params['bbox'] = '5,-10,6,-9' - resp = self.app.get(self.common_map_req) + def test_get_map_direct_fwd_params_layer(self, app): + img = create_tmp_image((200, 200), format="png") + expected_req = ( + { + "path": r"/service?LAYERs=bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=-180.0,0.0,0.0,80.0" + "&WIDTH=200&TIME=20041012" + }, + {"body": img}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + self.common_map_req.params["layers"] = "direct_fwd_params" + self.common_map_req.params["time"] = "20041012" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" + + def test_get_map_use_direct_from_level(self, app): + with tmp_image((200, 200), format="png") as img: + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A4326&styles=" + "&VERSION=1.1.1&BBOX=5.0,-10.0,6.0,-9.0" + "&WIDTH=200" + }, + {"body": img.read(), "headers": {"content-type": "image/png"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + self.common_map_req.params["bbox"] = "5,-10,6,-9" + resp = app.get(self.common_map_req) img.seek(0) assert resp.body == img.read() is_png(img) - eq_(resp.content_type, 'image/png') + assert resp.content_type == "image/png" - def test_get_map_use_direct_from_level_with_transform(self): - with tmp_image((200, 200), format='png') as img: - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A900913&styles=' - '&VERSION=1.1.1&BBOX=908822.944624,7004479.85652,920282.144964,7014491.63726' - '&WIDTH=229'}, - {'body': img.read(), 'headers': {'content-type': 'image/png'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - self.common_map_req.params['bbox'] = '444122.311736,5885498.04243,450943.508884,5891425.10484' - self.common_map_req.params['srs'] = 'EPSG:25832' - resp = self.app.get(self.common_map_req) + def test_get_map_use_direct_from_level_with_transform(self, app): + with tmp_image((200, 200), format="png") as img: + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=200&SRS=EPSG%3A900913&styles=" + "&VERSION=1.1.1&BBOX=908822.944624,7004479.85652,920282.144964,7014491.63726" + "&WIDTH=229" + }, + {"body": img.read(), "headers": {"content-type": "image/png"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + self.common_map_req.params[ + "bbox" + ] = "444122.311736,5885498.04243,450943.508884,5891425.10484" + self.common_map_req.params["srs"] = "EPSG:25832" + resp = app.get(self.common_map_req) img.seek(0) assert resp.body != img.read() is_png(img) - eq_(resp.content_type, 'image/png') + assert resp.content_type == "image/png" - def test_get_map_invalid_bbox(self): + def test_get_map_invalid_bbox(self, app): # min x larger than max x - url = """/service?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&BBOX=7,2,-9,10&SRS=EPSG:4326&WIDTH=164&HEIGHT=388&LAYERS=wms_cache&STYLES=&FORMAT=image/png&TRANSPARENT=TRUE""" - resp = self.app.get(url) - is_111_exception(resp.lxml, 'invalid bbox 7,2,-9,10') + url = ( + """/service?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&BBOX=7,2,-9,10&SRS=EPSG:4326&WIDTH=164&HEIGHT=388&LAYERS=wms_cache&STYLES=&FORMAT=image/png&TRANSPARENT=TRUE""" + ) + resp = app.get(url) + is_111_exception(resp.lxml, "invalid bbox 7,2,-9,10") - def test_get_map_invalid_bbox2(self): + def test_get_map_invalid_bbox2(self, app): # broken bbox for the requested srs - url = """/service?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&BBOX=-72988843.697212,-255661507.634227,142741550.188860,255661507.634227&SRS=EPSG:25833&WIDTH=164&HEIGHT=388&LAYERS=wms_cache_100&STYLES=&FORMAT=image/png&TRANSPARENT=TRUE""" - resp = self.app.get(url) + url = ( + """/service?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&BBOX=-72988843.697212,-255661507.634227,142741550.188860,255661507.634227&SRS=EPSG:25833&WIDTH=164&HEIGHT=388&LAYERS=wms_cache_100&STYLES=&FORMAT=image/png&TRANSPARENT=TRUE""" + ) + resp = app.get(url) # result depends on proj version - is_111_exception(resp.lxml, re_msg='Request too large or invalid BBOX.|Could not transform BBOX: Invalid result.') - - def test_get_map_broken_bbox(self): - url = """/service?VERSION=1.1.11&REQUEST=GetMap&SRS=EPSG:31468&BBOX=-10000855.0573254,2847125.18913603,-9329367.42767611,4239924.78564583&WIDTH=130&HEIGHT=62&LAYERS=wms_cache&STYLES=&FORMAT=image/png&TRANSPARENT=TRUE""" - resp = self.app.get(url) - is_111_exception(resp.lxml, 'Could not transform BBOX: Invalid result.') + is_111_exception( + resp.lxml, + re_msg="Request too large or invalid BBOX.|Could not transform BBOX: Invalid result.", + ) + + def test_get_map_broken_bbox(self, app): + url = ( + """/service?VERSION=1.1.11&REQUEST=GetMap&SRS=EPSG:31468&BBOX=-10000855.0573254,2847125.18913603,-9329367.42767611,4239924.78564583&WIDTH=130&HEIGHT=62&LAYERS=wms_cache&STYLES=&FORMAT=image/png&TRANSPARENT=TRUE""" + ) + resp = app.get(url) + is_111_exception(resp.lxml, "Could not transform BBOX: Invalid result.") - def test_get_map100(self): + def test_get_map100(self, app, base_dir, cache_dir): # check global tile lock directory - tiles_lock_dir = os.path.join(test_config['base_dir'], 'defaulttilelockdir') + tiles_lock_dir = base_dir.join("defaulttilelockdir") # make sure global tile_lock_dir was ot created by other tests - shutil.rmtree(tiles_lock_dir, ignore_errors=True) - assert not os.path.exists(tiles_lock_dir) + if tiles_lock_dir.check(): + tiles_lock_dir.remove() - self.created_tiles.append('wms_cache_100_EPSG900913/01/000/000/001/000/000/001.jpeg') # request_format tiff, cache format jpeg, wms request in png - with tmp_image((256, 256), format='tiff') as img: - expected_req = ({'path': r'/service?LAYERs=foo,bar&FORMAT=TIFF' - '&REQUEST=map&HEIGHT=256&SRS=EPSG%3A900913&styles=' - '&WMTVER=1.0.0&BBOX=0.0,0.0,20037508.3428,20037508.3428' - '&WIDTH=256'}, - {'body': img.read(), 'headers': {'content-type': 'image/tiff'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - self.common_map_req.params['bbox'] = '0,0,180,90' - self.common_map_req.params['layers'] = 'wms_cache_100' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + with tmp_image((256, 256), format="tiff") as img: + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&FORMAT=TIFF" + "&REQUEST=map&HEIGHT=256&SRS=EPSG%3A900913&styles=" + "&WMTVER=1.0.0&BBOX=0.0,0.0,20037508.3428,20037508.3428" + "&WIDTH=256" + }, + {"body": img.read(), "headers": {"content-type": "image/tiff"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + self.common_map_req.params["bbox"] = "0,0,180,90" + self.common_map_req.params["layers"] = "wms_cache_100" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" # check global tile lock directory was created - assert os.path.exists(tiles_lock_dir) + assert tiles_lock_dir.check() - def test_get_map130(self): - self.created_tiles.append('wms_cache_130_EPSG900913/01/000/000/001/000/000/001.jpeg') - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg' - '&REQUEST=GetMap&HEIGHT=256&CRS=EPSG%3A900913&styles=' - '&VERSION=1.3.0&BBOX=0.0,0.0,20037508.3428,20037508.3428' - '&WIDTH=256'}, - {'body': img.read(), 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - self.common_map_req.params['bbox'] = '0,0,180,90' - self.common_map_req.params['layers'] = 'wms_cache_130' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') - - def test_get_map130_axis_order(self): - self.created_tiles.append('wms_cache_multi_EPSG4326/02/000/000/003/000/000/001.jpeg') - with tmp_image((256, 256), format='jpeg') as img: + assert cache_dir.join( + "wms_cache_100_EPSG900913/01/000/000/001/000/000/001.jpeg" + ).check() + + def test_get_map130(self, app, cache_dir): + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg" + "&REQUEST=GetMap&HEIGHT=256&CRS=EPSG%3A900913&styles=" + "&VERSION=1.3.0&BBOX=0.0,0.0,20037508.3428,20037508.3428" + "&WIDTH=256" + }, + {"body": img.read(), "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + self.common_map_req.params["bbox"] = "0,0,180,90" + self.common_map_req.params["layers"] = "wms_cache_130" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" + assert cache_dir.join( + "wms_cache_130_EPSG900913/01/000/000/001/000/000/001.jpeg" + ).check() + + def test_get_map130_axis_order(self, app, cache_dir): + with tmp_image((256, 256), format="jpeg") as img: img = img.read() - expected_reqs = [({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg' - '&REQUEST=GetMap&HEIGHT=256&CRS=EPSG%3A4326&styles=' - '&VERSION=1.3.0&BBOX=0.0,90.0,90.0,180.0' - '&WIDTH=256'}, - {'body': img, 'headers': {'content-type': 'image/jpeg'}}),] - with mock_httpd(('localhost', 42423), expected_reqs): - self.common_map_req.params['bbox'] = '90,0,180,90' - self.common_map_req.params['layers'] = 'wms_cache_multi' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') - - def test_get_featureinfo(self): - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913' - '&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20&feature_count=100'}, - {'body': b'info', 'headers': {'content-type': 'text/plain'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - self.common_fi_req.params['feature_count'] = 100 - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'text/plain') - eq_(resp.body, b'info') - - - def test_get_featureinfo_float(self): - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913' - '&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=foo,bar&X=10.123&Y=20.567&feature_count=100'}, - {'body': b'info', 'headers': {'content-type': 'text/plain'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - self.common_fi_req.params['feature_count'] = 100 - self.common_fi_req.params['x'] = 10.123 - self.common_fi_req.params['y'] = 20.567 - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'text/plain') - eq_(resp.body, b'info') - - def test_get_featureinfo_transformed(self): - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913' - '&BBOX=1172272.30156,7196018.03449,1189711.04571,7213496.99738' - '&styles=&VERSION=1.1.1&feature_count=100' - '&WIDTH=200&QUERY_LAYERS=foo,bar&X=14&Y=20'}, - {'body': b'info', 'headers': {'content-type': 'text/plain'}}) + expected_reqs = [ + ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg" + "&REQUEST=GetMap&HEIGHT=256&CRS=EPSG%3A4326&styles=" + "&VERSION=1.3.0&BBOX=0.0,90.0,90.0,180.0" + "&WIDTH=256" + }, + {"body": img, "headers": {"content-type": "image/jpeg"}}, + ) + ] + with mock_httpd(("localhost", 42423), expected_reqs): + self.common_map_req.params["bbox"] = "90,0,180,90" + self.common_map_req.params["layers"] = "wms_cache_multi" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" + assert cache_dir.join( + "wms_cache_multi_EPSG4326/02/000/000/003/000/000/001.jpeg" + ).check() + + def test_get_featureinfo(self, app): + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913" + "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20&feature_count=100" + }, + {"body": b"info", "headers": {"content-type": "text/plain"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + self.common_fi_req.params["feature_count"] = 100 + resp = app.get(self.common_fi_req) + assert resp.content_type == "text/plain" + assert resp.body == b"info" + + def test_get_featureinfo_coverage(self, app): + self.common_fi_req.params["bbox"] = "-180.0,-90.0,180.0,90.0" + self.common_fi_req.params["srs"] = "EPSG:4326" + self.common_fi_req.params["width"] = "400" + self.common_fi_req.params["height"] = "200" + self.common_fi_req.params["x"] = 395 # outside of coverage + self.common_fi_req.params["y"] = 50 + self.common_fi_req.params["layers"] = 'tms_fi_cache' + self.common_fi_req.params["query_layers"] = 'tms_fi_cache' + + resp = app.get(self.common_fi_req) + assert resp.body == b"" + assert resp.content_type == "text/plain" + + expected_req = ( + { + "path": r"/service?LAYERs=fi&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A4326" + "&VERSION=1.1.1&BBOX=-180.0,-90.0,180.0,90.0&styles=" + "&WIDTH=400&QUERY_LAYERS=fi&X=380&Y=50" + }, + {"body": b"info", "headers": {"content-type": "text/plain"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + self.common_fi_req.params["x"] = 380 # inside of coverage + + resp = app.get(self.common_fi_req) + assert resp.body == b"info" + assert resp.content_type == "text/plain" + + def test_get_featureinfo_float(self, app): + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913" + "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=foo,bar&X=10.123&Y=20.567&feature_count=100" + }, + {"body": b"info", "headers": {"content-type": "text/plain"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + self.common_fi_req.params["feature_count"] = 100 + self.common_fi_req.params["x"] = 10.123 + self.common_fi_req.params["y"] = 20.567 + resp = app.get(self.common_fi_req) + assert resp.content_type == "text/plain" + assert resp.body == b"info" + + def test_get_featureinfo_transformed(self, app): + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913" + "&BBOX=1172272.30156,7196018.03449,1189711.04571,7213496.99738" + "&styles=&VERSION=1.1.1&feature_count=100" + "&WIDTH=200&QUERY_LAYERS=foo,bar&X=14&Y=20" + }, + {"body": b"info", "headers": {"content-type": "text/plain"}}, + ) # out fi point at x=10,y=20 - p_25832 = (600000+10*(610000 - 600000)/200, 6010000-20*(6010000 - 6000000)/200) + p_25832 = ( + 600000 + 10 * (610000 - 600000) / 200, + 6010000 - 20 * (6010000 - 6000000) / 200, + ) # the transformed fi point at x=14,y=20 - p_900913 = (1172272.30156+14*(1189711.04571-1172272.30156)/200, - 7213496.99738-20*(7213496.99738 - 7196018.03449)/200) + p_900913 = ( + 1172272.30156 + 14 * (1189711.04571 - 1172272.30156) / 200, + 7213496.99738 - 20 * (7213496.99738 - 7196018.03449) / 200, + ) # are they the same? # check with tolerance: pixel resolution is ~50 and x/y position is rounded to pizel assert abs(SRS(25832).transform_to(SRS(900913), p_25832)[0] - p_900913[0]) < 50 assert abs(SRS(25832).transform_to(SRS(900913), p_25832)[1] - p_900913[1]) < 50 - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - self.common_fi_req.params['bbox'] = '600000,6000000,610000,6010000' - self.common_fi_req.params['srs'] = 'EPSG:25832' + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + self.common_fi_req.params["bbox"] = "600000,6000000,610000,6010000" + self.common_fi_req.params["srs"] = "EPSG:25832" self.common_fi_req.params.pos = 10, 20 - self.common_fi_req.params['feature_count'] = 100 - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'text/plain') - eq_(resp.body, b'info') - - def test_get_featureinfo_info_format(self): - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913' - '&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20' - '&info_format=text%2Fhtml'}, - {'body': b'info', 'headers': {'content-type': 'text/html'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - self.common_fi_req.params['info_format'] = 'text/html' - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'text/html') - eq_(resp.body, b'info') - - def test_get_featureinfo_130(self): - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913' - '&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=foo,bar&I=10&J=20'}, - {'body': b'info', 'headers': {'content-type': 'text/plain'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - self.common_fi_req.params['layers'] = 'wms_cache_130' - self.common_fi_req.params['query_layers'] = 'wms_cache_130' - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'text/plain') - eq_(resp.body, b'info') + self.common_fi_req.params["feature_count"] = 100 + resp = app.get(self.common_fi_req) + assert resp.content_type == "text/plain" + assert resp.body == b"info" - def test_get_featureinfo_missing_params(self): + def test_get_featureinfo_info_format(self, app): expected_req = ( - {'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913' - '&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20'}, - {'body': b'info', 'headers': {'content-type': 'text/plain'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - del self.common_fi_req.params['format'] - del self.common_fi_req.params['styles'] - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'text/plain') - eq_(resp.body, b'info') + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913" + "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20" + "&info_format=text%2Fhtml" + }, + {"body": b"info", "headers": {"content-type": "text/html"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + self.common_fi_req.params["info_format"] = "text/html" + resp = app.get(self.common_fi_req) + assert resp.content_type == "text/html" + assert resp.body == b"

info

" - def test_get_featureinfo_missing_params_strict(self): - request_parser = self.app.app.handlers['service'].services['wms'].request_parser - try: - self.app.app.handlers['service'].services['wms'].request_parser = \ - functools.partial(wms_request, strict=True) + def test_get_featureinfo_130(self, app): + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913" + "&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=foo,bar&I=10&J=20" + }, + {"body": b"info", "headers": {"content-type": "text/plain"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + self.common_fi_req.params["layers"] = "wms_cache_130" + self.common_fi_req.params["query_layers"] = "wms_cache_130" + resp = app.get(self.common_fi_req) + assert resp.content_type == "text/plain" + assert resp.body == b"info" - del self.common_fi_req.params['format'] - del self.common_fi_req.params['styles'] - resp = self.app.get(self.common_fi_req) + def test_get_featureinfo_missing_params(self, app): + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913" + "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20" + }, + {"body": b"info", "headers": {"content-type": "text/plain"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + del self.common_fi_req.params["format"] + del self.common_fi_req.params["styles"] + resp = app.get(self.common_fi_req) + assert resp.content_type == "text/plain" + assert resp.body == b"info" + + def test_get_featureinfo_missing_params_strict(self, app): + request_parser = app.app.handlers["service"].services["wms"].request_parser + try: + app.app.handlers["service"].services[ + "wms" + ].request_parser = functools.partial(wms_request, strict=True) + + del self.common_fi_req.params["format"] + del self.common_fi_req.params["styles"] + resp = app.get(self.common_fi_req) xml = resp.lxml - assert 'missing parameters' in xml.xpath('//ServiceException/text()')[0] - assert validate_with_dtd(xml, 'wms/1.1.1/exception_1_1_1.dtd') + assert "missing parameters" in xml.xpath("//ServiceException/text()")[0] + assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd") finally: - self.app.app.handlers['service'].services['wms'].request_parser = request_parser - self.app.app.handlers['service'].request_parser = request_parser + app.app.handlers["service"].services["wms"].request_parser = request_parser + app.app.handlers["service"].request_parser = request_parser + + def test_get_featureinfo_not_queryable(self, app): + self.common_fi_req.params["query_layers"] = "tms_cache" + self.common_fi_req.params["exceptions"] = "application/vnd.ogc.se_xml" + resp = app.get(self.common_fi_req) + assert resp.content_type == "application/vnd.ogc.se_xml" + xml = resp.lxml + assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == [] + assert "tms_cache is not queryable" in xml.xpath("//ServiceException/text()")[0] + assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd") - def test_get_featureinfo_not_queryable(self): - self.common_fi_req.params['query_layers'] = 'tms_cache' - self.common_fi_req.params['exceptions'] = 'application/vnd.ogc.se_xml' - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - xml = resp.lxml - eq_(xml.xpath('/ServiceExceptionReport/ServiceException/@code'), []) - assert 'tms_cache is not queryable' in xml.xpath('//ServiceException/text()')[0] - assert validate_with_dtd(xml, 'wms/1.1.1/exception_1_1_1.dtd') +class TestWMS110(SysTest): + config_file = "layer.yaml" -class TestWMS110(WMSTest): def setup(self): - WMSTest.setup(self) - self.common_req = WMS110MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.0')) - self.common_map_req = WMS110MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.0', bbox='-180,0,0,80', width='200', height='200', - layers='wms_cache', srs='EPSG:4326', format='image/png', - styles='', request='GetMap')) - self.common_fi_req = WMS110FeatureInfoRequest(url='/service?', - param=dict(x='10', y='20', width='200', height='200', layers='wms_cache', - format='image/png', query_layers='wms_cache_110', styles='', - bbox='1000,400,2000,1400', srs='EPSG:900913')) - - def test_wms_capabilities(self): - req = WMS110CapabilitiesRequest(url='/service?').copy_with_request_params(self.common_req) - resp = self.app.get(req) - eq_(resp.content_type, 'application/vnd.ogc.wms_xml') - xml = resp.lxml - eq_(xml.xpath('//GetMap//OnlineResource/@xlink:href', - namespaces=dict(xlink="http://www.w3.org/1999/xlink"))[0], - 'http://localhost/service?') + # WMSTest.setup(self) + self.common_req = WMS110MapRequest( + url="/service?", param=dict(service="WMS", version="1.1.0") + ) + self.common_map_req = WMS110MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.0", + bbox="-180,0,0,80", + width="200", + height="200", + layers="wms_cache", + srs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + ), + ) + self.common_fi_req = WMS110FeatureInfoRequest( + url="/service?", + param=dict( + x="10", + y="20", + width="200", + height="200", + layers="wms_cache", + format="image/png", + query_layers="wms_cache_110", + styles="", + bbox="1000,400,2000,1400", + srs="EPSG:900913", + ), + ) + + def test_wms_capabilities(self, app): + req = WMS110CapabilitiesRequest(url="/service?").copy_with_request_params( + self.common_req + ) + resp = app.get(req) + assert resp.content_type == "application/vnd.ogc.wms_xml" + xml = resp.lxml + assert ( + xml.xpath( + "//GetMap//OnlineResource/@xlink:href", + namespaces=dict(xlink="http://www.w3.org/1999/xlink"), + )[0] == + "http://localhost/service?" + ) - llbox = xml.xpath('//Capability/Layer/LatLonBoundingBox')[0] + llbox = xml.xpath("//Capability/Layer/LatLonBoundingBox")[0] # some clients don't like 90deg north/south - assert_almost_equal(float(llbox.attrib['miny']), -70.0, 6) - assert_almost_equal(float(llbox.attrib['maxy']), 89.999999, 6) - assert_almost_equal(float(llbox.attrib['minx']), -180.0, 6) - assert_almost_equal(float(llbox.attrib['maxx']), 180.0, 6) - - layer_names = set(xml.xpath('//Layer/Layer/Name/text()')) - expected_names = set(['direct_fwd_params', 'direct', 'wms_cache', - 'wms_cache_100', 'wms_cache_130', 'wms_cache_transparent', - 'wms_merge', 'tms_cache', 'tms_fi_cache', 'wms_cache_multi', - 'wms_cache_link_single', 'wms_cache_110', 'watermark_cache']) - eq_(layer_names, expected_names) - assert validate_with_dtd(xml, dtd_name='wms/1.1.0/capabilities_1_1_0.dtd') - - def test_invalid_layer(self): - self.common_map_req.params['layers'] = 'invalid' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - xml = resp.lxml - eq_(xml.xpath('/ServiceExceptionReport/@version')[0], '1.1.0') - eq_(xml.xpath('/ServiceExceptionReport/ServiceException/@code')[0], 'LayerNotDefined') - eq_(xml.xpath('//ServiceException/text()')[0], 'unknown layer: invalid') - assert validate_with_dtd(xml, dtd_name='wms/1.1.0/exception_1_1_0.dtd') - - def test_invalid_format(self): - self.common_map_req.params['format'] = 'image/ascii' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - xml = resp.lxml - eq_(xml.xpath('/ServiceExceptionReport/@version')[0], '1.1.0') - eq_(xml.xpath('/ServiceExceptionReport/ServiceException/@code')[0], 'InvalidFormat') - eq_(xml.xpath('//ServiceException/text()')[0], 'unsupported image format: image/ascii') - assert validate_with_dtd(xml, dtd_name='wms/1.1.0/exception_1_1_0.dtd') - - def test_invalid_format_img_exception(self): - self.common_map_req.params['format'] = 'image/ascii' - self.common_map_req.params['exceptions'] = 'application/vnd.ogc.se_inimage' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + assert float(llbox.attrib["miny"]) == pytest.approx(-70.0) + assert float(llbox.attrib["maxy"]) == pytest.approx(89.999999) + assert float(llbox.attrib["minx"]) == pytest.approx(-180.0) + assert float(llbox.attrib["maxx"]) == pytest.approx(180.0) + + layer_names = set(xml.xpath("//Layer/Layer/Name/text()")) + expected_names = set( + [ + "direct_fwd_params", + "direct", + "wms_cache", + "wms_cache_100", + "wms_cache_130", + "wms_cache_transparent", + "wms_merge", + "tms_cache", + "tms_fi_cache", + "wms_cache_multi", + "wms_cache_link_single", + "wms_cache_110", + "watermark_cache", + ] + ) + assert layer_names == expected_names + assert validate_with_dtd(xml, dtd_name="wms/1.1.0/capabilities_1_1_0.dtd") + + def test_invalid_layer(self, app): + self.common_map_req.params["layers"] = "invalid" + resp = app.get(self.common_map_req) + assert resp.content_type == "application/vnd.ogc.se_xml" + xml = resp.lxml + assert xml.xpath("/ServiceExceptionReport/@version")[0] == "1.1.0" + assert ( + xml.xpath("/ServiceExceptionReport/ServiceException/@code")[0] == + "LayerNotDefined" + ) + assert xml.xpath("//ServiceException/text()")[0] == "unknown layer: invalid" + assert validate_with_dtd(xml, dtd_name="wms/1.1.0/exception_1_1_0.dtd") + + def test_invalid_format(self, app): + self.common_map_req.params["format"] = "image/ascii" + resp = app.get(self.common_map_req) + assert resp.content_type == "application/vnd.ogc.se_xml" + xml = resp.lxml + assert xml.xpath("/ServiceExceptionReport/@version")[0] == "1.1.0" + assert ( + xml.xpath("/ServiceExceptionReport/ServiceException/@code")[0] == + "InvalidFormat" + ) + assert ( + xml.xpath("//ServiceException/text()")[0] == + "unsupported image format: image/ascii" + ) + assert validate_with_dtd(xml, dtd_name="wms/1.1.0/exception_1_1_0.dtd") + + def test_invalid_format_img_exception(self, app): + self.common_map_req.params["format"] = "image/ascii" + self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" assert is_png(BytesIO(resp.body)) - def test_missing_format_img_exception(self): - del self.common_map_req.params['format'] - self.common_map_req.params['exceptions'] = 'application/vnd.ogc.se_inimage' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_missing_format_img_exception(self, app): + del self.common_map_req.params["format"] + self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" assert is_png(BytesIO(resp.body)) - def test_invalid_srs(self): - self.common_map_req.params['srs'] = 'EPSG:1234' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - xml = resp.lxml - eq_(xml.xpath('/ServiceExceptionReport/@version')[0], '1.1.0') - eq_(xml.xpath('/ServiceExceptionReport/ServiceException/@code')[0], 'InvalidSRS') - eq_(xml.xpath('//ServiceException/text()')[0], 'unsupported srs: EPSG:1234') - assert validate_with_dtd(xml, dtd_name='wms/1.1.0/exception_1_1_0.dtd') - - def test_get_map_png(self): - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_invalid_srs(self, app): + self.common_map_req.params["srs"] = "EPSG:1234" + resp = app.get(self.common_map_req) + assert resp.content_type == "application/vnd.ogc.se_xml" + xml = resp.lxml + assert xml.xpath("/ServiceExceptionReport/@version")[0] == "1.1.0" + assert ( + xml.xpath("/ServiceExceptionReport/ServiceException/@code")[0] == "InvalidSRS" + ) + assert xml.xpath("//ServiceException/text()")[0] == "unsupported srs: EPSG:1234" + assert validate_with_dtd(xml, dtd_name="wms/1.1.0/exception_1_1_0.dtd") + + def test_get_map_png(self, app, fixture_cache_data): + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - assert Image.open(data).mode == 'RGB' + assert Image.open(data).mode == "RGB" - def test_get_map_jpeg(self): - self.common_map_req.params['format'] = 'image/jpeg' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/jpeg') + def test_get_map_jpeg(self, app, fixture_cache_data): + self.common_map_req.params["format"] = "image/jpeg" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/jpeg" assert is_jpeg(BytesIO(resp.body)) - def test_get_map_xml_exception(self): - self.common_map_req.params['bbox'] = '0,0,90,90' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - xml = resp.lxml - eq_(xml.xpath('/ServiceExceptionReport/ServiceException/@code'), []) - assert 'No response from URL' in xml.xpath('//ServiceException/text()')[0] - assert validate_with_dtd(xml, 'wms/1.1.0/exception_1_1_0.dtd') - - def test_get_map(self): - self.created_tiles.append('wms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg') - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg' - '&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=' - '&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428' - '&WIDTH=256'}, - {'body': img.read(), 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - self.common_map_req.params['bbox'] = '0,0,180,90' - resp = self.app.get(self.common_map_req) - assert 35000 < int(resp.headers['Content-length']) < 75000 - eq_(resp.content_type, 'image/png') - - def test_get_map_110(self): - self.created_tiles.append('wms_cache_110_EPSG900913/01/000/000/001/000/000/001.jpeg') - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg' - '&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=' - '&VERSION=1.1.0&BBOX=0.0,0.0,20037508.3428,20037508.3428' - '&WIDTH=256'}, - {'body': img.read(), 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - self.common_map_req.params['bbox'] = '0,0,180,90' - self.common_map_req.params['layers'] = 'wms_cache_110' - resp = self.app.get(self.common_map_req) - assert 35000 < int(resp.headers['Content-length']) < 75000 - eq_(resp.content_type, 'image/png') - - def test_get_featureinfo(self): - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913' - '&VERSION=1.1.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20'}, - {'body': b'info', 'headers': {'content-type': 'text/plain'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'text/plain') - eq_(resp.body, b'info') - - def test_get_featureinfo_not_queryable(self): - self.common_fi_req.params['query_layers'] = 'tms_cache' - self.common_fi_req.params['exceptions'] = 'application/vnd.ogc.se_xml' - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'application/vnd.ogc.se_xml') - xml = resp.lxml - eq_(xml.xpath('/ServiceExceptionReport/ServiceException/@code'), []) - assert 'tms_cache is not queryable' in xml.xpath('//ServiceException/text()')[0] - assert validate_with_dtd(xml, 'wms/1.1.0/exception_1_1_0.dtd') + def test_get_map_xml_exception(self, app): + self.common_map_req.params["bbox"] = "0,0,90,90" + resp = app.get(self.common_map_req) + assert resp.content_type == "application/vnd.ogc.se_xml" + xml = resp.lxml + assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == [] + assert "No response from URL" in xml.xpath("//ServiceException/text()")[0] + assert validate_with_dtd(xml, "wms/1.1.0/exception_1_1_0.dtd") + + def test_get_map(self, app, cache_dir): + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg" + "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=" + "&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428" + "&WIDTH=256" + }, + {"body": img.read(), "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + self.common_map_req.params["bbox"] = "0,0,180,90" + resp = app.get(self.common_map_req) + assert 35000 < int(resp.headers["Content-length"]) < 75000 + assert resp.content_type == "image/png" + + assert cache_dir.join( + "wms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg" + ).check() + + def test_get_map_110(self, app, cache_dir): + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg" + "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=" + "&VERSION=1.1.0&BBOX=0.0,0.0,20037508.3428,20037508.3428" + "&WIDTH=256" + }, + {"body": img.read(), "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + self.common_map_req.params["bbox"] = "0,0,180,90" + self.common_map_req.params["layers"] = "wms_cache_110" + resp = app.get(self.common_map_req) + assert 35000 < int(resp.headers["Content-length"]) < 75000 + assert resp.content_type == "image/png" + + assert cache_dir.join( + "wms_cache_110_EPSG900913/01/000/000/001/000/000/001.jpeg" + ).check() + + def test_get_featureinfo(self, app): + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913" + "&VERSION=1.1.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20" + }, + {"body": b"info", "headers": {"content-type": "text/plain"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + resp = app.get(self.common_fi_req) + assert resp.content_type == "text/plain" + assert resp.body == b"info" + + def test_get_featureinfo_not_queryable(self, app): + self.common_fi_req.params["query_layers"] = "tms_cache" + self.common_fi_req.params["exceptions"] = "application/vnd.ogc.se_xml" + resp = app.get(self.common_fi_req) + assert resp.content_type == "application/vnd.ogc.se_xml" + xml = resp.lxml + assert xml.xpath("/ServiceExceptionReport/ServiceException/@code") == [] + assert "tms_cache is not queryable" in xml.xpath("//ServiceException/text()")[0] + assert validate_with_dtd(xml, "wms/1.1.0/exception_1_1_0.dtd") + +class TestWMS100(SysTest): + config_file = "layer.yaml" -class TestWMS100(WMSTest): def setup(self): - WMSTest.setup(self) - self.common_req = WMS100MapRequest(url='/service?', param=dict(wmtver='1.0.0')) - self.common_map_req = WMS100MapRequest(url='/service?', param=dict(wmtver='1.0.0', - bbox='-180,0,0,80', width='200', height='200', - layers='wms_cache', srs='EPSG:4326', format='PNG', - styles='', request='GetMap')) - self.common_fi_req = WMS100FeatureInfoRequest(url='/service?', - param=dict(x='10', y='20', width='200', height='200', layers='wms_cache_100', - format='PNG', query_layers='wms_cache_100', styles='', - bbox='1000,400,2000,1400', srs='EPSG:900913')) - - def test_wms_capabilities(self): - req = WMS100CapabilitiesRequest(url='/service?').copy_with_request_params(self.common_req) - resp = self.app.get(req) - eq_(resp.content_type, 'text/xml') - xml = resp.lxml - eq_(xml.xpath('/WMT_MS_Capabilities/Service/Title/text()')[0], - u'MapProxy test fixture \u2603') - layer_names = set(xml.xpath('//Layer/Layer/Name/text()')) - expected_names = set(['direct_fwd_params', 'direct', 'wms_cache', - 'wms_cache_100', 'wms_cache_130', 'wms_cache_transparent', - 'wms_merge', 'tms_cache', 'tms_fi_cache', 'wms_cache_multi', - 'wms_cache_link_single', 'wms_cache_110', 'watermark_cache']) - eq_(layer_names, expected_names) - #TODO srs - assert validate_with_dtd(xml, dtd_name='wms/1.0.0/capabilities_1_0_0.dtd') - - - def test_invalid_layer(self): - self.common_map_req.params['layers'] = 'invalid' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'text/xml') - xml = resp.lxml - eq_(xml.xpath('/WMTException/@version')[0], '1.0.0') - eq_(xml.xpath('//WMTException/text()')[0].strip(), 'unknown layer: invalid') - - def test_invalid_format(self): - self.common_map_req.params['format'] = 'image/ascii' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'text/xml') - xml = resp.lxml - eq_(xml.xpath('/WMTException/@version')[0], '1.0.0') - eq_(xml.xpath('//WMTException/text()')[0].strip(), - 'unsupported image format: ASCII') - - def test_invalid_format_img_exception(self): - self.common_map_req.params['format'] = 'image/ascii' - self.common_map_req.params['exceptions'] = 'INIMAGE' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + self.common_req = WMS100MapRequest(url="/service?", param=dict(wmtver="1.0.0")) + self.common_map_req = WMS100MapRequest( + url="/service?", + param=dict( + wmtver="1.0.0", + bbox="-180,0,0,80", + width="200", + height="200", + layers="wms_cache", + srs="EPSG:4326", + format="PNG", + styles="", + request="GetMap", + ), + ) + self.common_fi_req = WMS100FeatureInfoRequest( + url="/service?", + param=dict( + x="10", + y="20", + width="200", + height="200", + layers="wms_cache_100", + format="PNG", + query_layers="wms_cache_100", + styles="", + bbox="1000,400,2000,1400", + srs="EPSG:900913", + ), + ) + + def test_wms_capabilities(self, app): + req = WMS100CapabilitiesRequest(url="/service?").copy_with_request_params( + self.common_req + ) + resp = app.get(req) + assert resp.content_type == "text/xml" + xml = resp.lxml + assert ( + xml.xpath("/WMT_MS_Capabilities/Service/Title/text()")[0] == + u"MapProxy test fixture \u2603" + ) + layer_names = set(xml.xpath("//Layer/Layer/Name/text()")) + expected_names = set( + [ + "direct_fwd_params", + "direct", + "wms_cache", + "wms_cache_100", + "wms_cache_130", + "wms_cache_transparent", + "wms_merge", + "tms_cache", + "tms_fi_cache", + "wms_cache_multi", + "wms_cache_link_single", + "wms_cache_110", + "watermark_cache", + ] + ) + assert layer_names == expected_names + # TODO srs + assert validate_with_dtd(xml, dtd_name="wms/1.0.0/capabilities_1_0_0.dtd") + + def test_invalid_layer(self, app): + self.common_map_req.params["layers"] = "invalid" + resp = app.get(self.common_map_req) + assert resp.content_type == "text/xml" + xml = resp.lxml + assert xml.xpath("/WMTException/@version")[0] == "1.0.0" + assert xml.xpath("//WMTException/text()")[0].strip() == "unknown layer: invalid" + + def test_invalid_format(self, app): + self.common_map_req.params["format"] = "image/ascii" + resp = app.get(self.common_map_req) + assert resp.content_type == "text/xml" + xml = resp.lxml + assert xml.xpath("/WMTException/@version")[0] == "1.0.0" + assert ( + xml.xpath("//WMTException/text()")[0].strip() == + "unsupported image format: ASCII" + ) + + def test_invalid_format_img_exception(self, app): + self.common_map_req.params["format"] = "image/ascii" + self.common_map_req.params["exceptions"] = "INIMAGE" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" assert is_png(BytesIO(resp.body)) - def test_missing_format_img_exception(self): - del self.common_map_req.params['format'] - self.common_map_req.params['exceptions'] = 'INIMAGE' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_missing_format_img_exception(self, app): + del self.common_map_req.params["format"] + self.common_map_req.params["exceptions"] = "INIMAGE" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" assert is_png(BytesIO(resp.body)) - def test_invalid_srs(self): - self.common_map_req.params['srs'] = 'EPSG:1234' + def test_invalid_srs(self, app): + self.common_map_req.params["srs"] = "EPSG:1234" print(self.common_map_req.complete_url) - resp = self.app.get(self.common_map_req.complete_url) + resp = app.get(self.common_map_req.complete_url) xml = resp.lxml - eq_(xml.xpath('//WMTException/text()')[0].strip(), 'unsupported srs: EPSG:1234') + assert xml.xpath("//WMTException/text()")[0].strip() == "unsupported srs: EPSG:1234" - def test_get_map_png(self): - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_get_map_png(self, app, fixture_cache_data): + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - eq_(Image.open(data).mode, 'RGB') + assert Image.open(data).mode == "RGB" - def test_get_map_png_transparent_paletted(self): + def test_get_map_png_transparent_paletted( + self, app, base_config, fixture_cache_data + ): try: - base_config().image.paletted = True - self.common_map_req.params['transparent'] = 'True' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + base_config.image.paletted = True + self.common_map_req.params["transparent"] = "True" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - assert Image.open(data).mode == 'P' + assert Image.open(data).mode == "P" finally: - base_config().image.paletted = False + base_config.image.paletted = False - def test_get_map_jpeg(self): - self.common_map_req.params['format'] = 'image/jpeg' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/jpeg') + def test_get_map_jpeg(self, app, fixture_cache_data): + self.common_map_req.params["format"] = "image/jpeg" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/jpeg" assert is_jpeg(BytesIO(resp.body)) - def test_get_map_xml_exception(self): - self.common_map_req.params['bbox'] = '0,0,90,90' - resp = self.app.get(self.common_map_req) - xml = resp.lxml - assert 'No response from URL' in xml.xpath('//WMTException/text()')[0] - - def test_get_map(self): - self.created_tiles.append('wms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg') - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg' - '&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=' - '&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428' - '&WIDTH=256'}, - {'body': img.read(), 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - self.common_map_req.params['bbox'] = '0,0,180,90' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') - - def test_get_featureinfo(self): - expected_req = ({'path': r'/service?LAYERs=foo,bar&FORMAT=image%2FPNG' # TODO should be PNG only - '&REQUEST=feature_info&HEIGHT=200&SRS=EPSG%3A900913' - '&WMTVER=1.0.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20'}, - {'body': b'info', 'headers': {'content-type': 'text/plain'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'text/plain') - eq_(resp.body, b'info') - - def test_get_featureinfo_not_queryable(self): - self.common_fi_req.params['query_layers'] = 'tms_cache' - self.common_fi_req.params['exceptions'] = 'application/vnd.ogc.se_xml' - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'text/xml') - xml = resp.lxml - assert 'tms_cache is not queryable' in xml.xpath('//WMTException/text()')[0] - -ns130 = {'wms': 'http://www.opengis.net/wms', - 'ogc': 'http://www.opengis.net/ogc', - 'sld': 'http://www.opengis.net/sld', - 'xlink': 'http://www.w3.org/1999/xlink'} + def test_get_map_xml_exception(self, app): + self.common_map_req.params["bbox"] = "0,0,90,90" + resp = app.get(self.common_map_req) + xml = resp.lxml + assert "No response from URL" in xml.xpath("//WMTException/text()")[0] + + def test_get_map(self, app, cache_dir): + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg" + "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=" + "&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428" + "&WIDTH=256" + }, + {"body": img.read(), "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + self.common_map_req.params["bbox"] = "0,0,180,90" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" + assert cache_dir.join( + "wms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg" + ).check() + + def test_get_featureinfo(self, app): + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&FORMAT=image%2FPNG" # TODO should be PNG only + "&REQUEST=feature_info&HEIGHT=200&SRS=EPSG%3A900913" + "&WMTVER=1.0.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20" + }, + {"body": b"info", "headers": {"content-type": "text/plain"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + resp = app.get(self.common_fi_req) + assert resp.content_type == "text/plain" + assert resp.body == b"info" + + def test_get_featureinfo_not_queryable(self, app): + self.common_fi_req.params["query_layers"] = "tms_cache" + self.common_fi_req.params["exceptions"] = "application/vnd.ogc.se_xml" + resp = app.get(self.common_fi_req) + assert resp.content_type == "text/xml" + xml = resp.lxml + assert "tms_cache is not queryable" in xml.xpath("//WMTException/text()")[0] -def eq_xpath(xml, xpath, expected, namespaces=None): - eq_(xml.xpath(xpath, namespaces=namespaces)[0], expected) -eq_xpath_wms130 = functools.partial(eq_xpath, namespaces=ns130) +ns130 = { + "wms": "http://www.opengis.net/wms", + "ogc": "http://www.opengis.net/ogc", + "sld": "http://www.opengis.net/sld", + "xlink": "http://www.w3.org/1999/xlink", +} + + +def assert_xpath(xml, xpath, expected, namespaces=None): + assert xml.xpath(xpath, namespaces=namespaces)[0] == expected + + +assert_xpath_wms130 = functools.partial(assert_xpath, namespaces=ns130) + + +class TestWMS130(SysTest): + config_file = "layer.yaml" -class TestWMS130(WMSTest): def setup(self): - WMSTest.setup(self) - self.common_req = WMS130MapRequest(url='/service?', param=dict(service='WMS', - version='1.3.0')) - self.common_map_req = WMS130MapRequest(url='/service?', param=dict(service='WMS', - version='1.3.0', bbox='0,-180,80,0', width='200', height='200', - layers='wms_cache', crs='EPSG:4326', format='image/png', - styles='', request='GetMap')) - self.common_fi_req = WMS130FeatureInfoRequest(url='/service?', - param=dict(i='10', j='20', width='200', height='200', layers='wms_cache_130', - format='image/png', query_layers='wms_cache_130', styles='', - bbox='1000,400,2000,1400', crs='EPSG:900913')) - - def test_wms_capabilities(self): - req = WMS130CapabilitiesRequest(url='/service?').copy_with_request_params(self.common_req) - resp = self.app.get(req) - eq_(resp.content_type, 'text/xml') - xml = resp.lxml - eq_xpath_wms130(xml, '/wms:WMS_Capabilities/wms:Service/wms:Title/text()', - u'MapProxy test fixture \u2603') + self.common_req = WMS130MapRequest( + url="/service?", param=dict(service="WMS", version="1.3.0") + ) + self.common_map_req = WMS130MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.3.0", + bbox="0,-180,80,0", + width="200", + height="200", + layers="wms_cache", + crs="EPSG:4326", + format="image/png", + styles="", + request="GetMap", + ), + ) + self.common_fi_req = WMS130FeatureInfoRequest( + url="/service?", + param=dict( + i="10", + j="20", + width="200", + height="200", + layers="wms_cache_130", + format="image/png", + query_layers="wms_cache_130", + styles="", + bbox="1000,400,2000,1400", + crs="EPSG:900913", + ), + ) + + def test_wms_capabilities(self, app): + req = WMS130CapabilitiesRequest(url="/service?").copy_with_request_params( + self.common_req + ) + resp = app.get(req) + assert resp.content_type == "text/xml" + xml = resp.lxml + assert_xpath_wms130( + xml, + "/wms:WMS_Capabilities/wms:Service/wms:Title/text()", + u"MapProxy test fixture \u2603", + ) # test for extended layer metadata - eq_xpath_wms130(xml, '/wms:WMS_Capabilities/wms:Capability/wms:Layer/wms:Layer/wms:Attribution/wms:Title/text()', - u'My attribution title') - - layer_names = set(xml.xpath('//wms:Layer/wms:Layer/wms:Name/text()', - namespaces=ns130)) - expected_names = set(['direct_fwd_params', 'direct', 'wms_cache', - 'wms_cache_100', 'wms_cache_130', 'wms_cache_transparent', - 'wms_merge', 'tms_cache', 'tms_fi_cache', 'wms_cache_multi', - 'wms_cache_link_single', 'wms_cache_110', 'watermark_cache']) - eq_(layer_names, expected_names) + assert_xpath_wms130( + xml, + "/wms:WMS_Capabilities/wms:Capability/wms:Layer/wms:Layer/wms:Attribution/wms:Title/text()", + u"My attribution title", + ) + + layer_names = set( + xml.xpath("//wms:Layer/wms:Layer/wms:Name/text()", namespaces=ns130) + ) + expected_names = set( + [ + "direct_fwd_params", + "direct", + "wms_cache", + "wms_cache_100", + "wms_cache_130", + "wms_cache_transparent", + "wms_merge", + "tms_cache", + "tms_fi_cache", + "wms_cache_multi", + "wms_cache_link_single", + "wms_cache_110", + "watermark_cache", + ] + ) + assert layer_names == expected_names assert is_130_capa(xml) - def test_invalid_layer(self): - self.common_map_req.params['layers'] = 'invalid' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'text/xml') - xml = resp.lxml - eq_xpath_wms130(xml, '/ogc:ServiceExceptionReport/@version', '1.3.0') - eq_xpath_wms130(xml, '/ogc:ServiceExceptionReport/ogc:ServiceException/@code', - 'LayerNotDefined') - eq_xpath_wms130(xml, '//ogc:ServiceException/text()', 'unknown layer: invalid') - assert validate_with_xsd(xml, xsd_name='wms/1.3.0/exceptions_1_3_0.xsd') - - def test_invalid_format(self): - self.common_map_req.params['format'] = 'image/ascii' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'text/xml') - xml = resp.lxml - eq_xpath_wms130(xml, '/ogc:ServiceExceptionReport/@version', '1.3.0') - eq_xpath_wms130(xml, '/ogc:ServiceExceptionReport/ogc:ServiceException/@code', - 'InvalidFormat') - eq_xpath_wms130(xml, '//ogc:ServiceException/text()', 'unsupported image format: image/ascii') - assert validate_with_xsd(xml, xsd_name='wms/1.3.0/exceptions_1_3_0.xsd') - - def test_invalid_format_img_exception(self): - self.common_map_req.params['format'] = 'image/ascii' - self.common_map_req.params['exceptions'] = 'application/vnd.ogc.se_inimage' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_invalid_layer(self, app): + self.common_map_req.params["layers"] = "invalid" + resp = app.get(self.common_map_req) + assert resp.content_type == "text/xml" + xml = resp.lxml + assert_xpath_wms130(xml, "/ogc:ServiceExceptionReport/@version", "1.3.0") + assert_xpath_wms130( + xml, + "/ogc:ServiceExceptionReport/ogc:ServiceException/@code", + "LayerNotDefined", + ) + assert_xpath_wms130(xml, "//ogc:ServiceException/text()", "unknown layer: invalid") + assert validate_with_xsd(xml, xsd_name="wms/1.3.0/exceptions_1_3_0.xsd") + + def test_invalid_format(self, app): + self.common_map_req.params["format"] = "image/ascii" + resp = app.get(self.common_map_req) + assert resp.content_type == "text/xml" + xml = resp.lxml + assert_xpath_wms130(xml, "/ogc:ServiceExceptionReport/@version", "1.3.0") + assert_xpath_wms130( + xml, + "/ogc:ServiceExceptionReport/ogc:ServiceException/@code", + "InvalidFormat", + ) + assert_xpath_wms130( + xml, + "//ogc:ServiceException/text()", + "unsupported image format: image/ascii", + ) + assert validate_with_xsd(xml, xsd_name="wms/1.3.0/exceptions_1_3_0.xsd") + + def test_invalid_format_img_exception(self, app): + self.common_map_req.params["format"] = "image/ascii" + self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" assert is_png(BytesIO(resp.body)) - def test_missing_format_img_exception(self): - del self.common_map_req.params['format'] - self.common_map_req.params['exceptions'] = 'application/vnd.ogc.se_inimage' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_missing_format_img_exception(self, app): + del self.common_map_req.params["format"] + self.common_map_req.params["exceptions"] = "application/vnd.ogc.se_inimage" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" assert is_png(BytesIO(resp.body)) - def test_invalid_srs(self): - self.common_map_req.params['srs'] = 'EPSG:1234' - self.common_map_req.params['exceptions'] = 'text/xml' - - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'text/xml') - xml = resp.lxml - eq_xpath_wms130(xml, '/ogc:ServiceExceptionReport/ogc:ServiceException/@code', - 'InvalidCRS') - eq_xpath_wms130(xml, '//ogc:ServiceException/text()', 'unsupported crs: EPSG:1234') - assert validate_with_xsd(xml, xsd_name='wms/1.3.0/exceptions_1_3_0.xsd') - - def test_get_map_png(self): - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_invalid_srs(self, app): + self.common_map_req.params["srs"] = "EPSG:1234" + self.common_map_req.params["exceptions"] = "text/xml" + + resp = app.get(self.common_map_req) + assert resp.content_type == "text/xml" + xml = resp.lxml + assert_xpath_wms130( + xml, "/ogc:ServiceExceptionReport/ogc:ServiceException/@code", "InvalidCRS" + ) + assert_xpath_wms130( + xml, "//ogc:ServiceException/text()", "unsupported crs: EPSG:1234" + ) + assert validate_with_xsd(xml, xsd_name="wms/1.3.0/exceptions_1_3_0.xsd") + + def test_get_map_png(self, app, fixture_cache_data): + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" data = BytesIO(resp.body) assert is_png(data) - assert Image.open(data).mode == 'RGB' + assert Image.open(data).mode == "RGB" - def test_get_map_jpeg(self): - self.common_map_req.params['format'] = 'image/jpeg' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/jpeg') + def test_get_map_jpeg(self, app, fixture_cache_data): + self.common_map_req.params["format"] = "image/jpeg" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/jpeg" assert is_jpeg(BytesIO(resp.body)) - def test_get_map_xml_exception(self): - self.common_map_req.params['bbox'] = '0,0,90,90' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'text/xml') - xml = resp.lxml - eq_(xml.xpath('/ogc:ServiceExceptionReport/ogc:ServiceException/@code', namespaces=ns130), []) - assert ('No response from URL' in - xml.xpath('//ogc:ServiceException/text()', namespaces=ns130)[0]) - assert validate_with_xsd(xml, xsd_name='wms/1.3.0/exceptions_1_3_0.xsd') - - def test_get_map(self): - self.created_tiles.append('wms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg') - with tmp_image((256, 256), format='jpeg') as img: - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg' - '&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=' - '&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428' - '&WIDTH=256'}, - {'body': img.read(), 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - self.common_map_req.params['bbox'] = '0,0,180,90' #internal axis-order - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') - - def test_get_featureinfo(self): - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913' - '&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=foo,bar&I=10&J=20'}, - {'body': b'info', 'headers': {'content-type': 'text/plain'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'text/plain') - eq_(resp.body, b'info') - - def test_get_featureinfo_111(self): - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913' - '&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20'}, - {'body': b'info', 'headers': {'content-type': 'text/plain'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - self.common_fi_req.params['layers'] = 'wms_cache' - self.common_fi_req.params['query_layers'] = 'wms_cache' - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'text/plain') - eq_(resp.body, b'info') - - -if sys.platform != 'win32': - class TestWMSLinkSingleColorImages(WMSTest): - def setup(self): - WMSTest.setup(self) - self.common_map_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1', bbox='-180,0,0,80', width='200', height='200', - layers='wms_cache_link_single', srs='EPSG:4326', format='image/jpeg', - styles='', request='GetMap')) - - def test_get_map(self): - link_name = 'wms_cache_link_single_EPSG900913/01/000/000/001/000/000/001.png' - real_name = 'wms_cache_link_single_EPSG900913/single_color_tiles/fe00a0.png' - self.created_tiles.append(link_name) - self.created_tiles.append(real_name) - with tmp_image((256, 256), format='jpeg', color='#fe00a0') as img: - expected_req = ({'path': r'/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg' - '&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=' - '&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428' - '&WIDTH=256'}, - {'body': img.read(), 'headers': {'content-type': 'image/jpeg'}}) - with mock_httpd(('localhost', 42423), [expected_req], bbox_aware_query_comparator=True): - self.common_map_req.params['bbox'] = '0,0,180,90' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/jpeg') - - base_dir = base_config().cache.base_dir - single_loc = os.path.join(base_dir, real_name) - tile_loc = os.path.join(base_dir, link_name) - assert os.path.exists(single_loc) - assert os.path.islink(tile_loc) - - self.common_map_req.params['format'] = 'image/png' - resp = self.app.get(self.common_map_req) - eq_(resp.content_type, 'image/png') + def test_get_map_xml_exception(self, app): + self.common_map_req.params["bbox"] = "0,0,90,90" + resp = app.get(self.common_map_req) + assert resp.content_type == "text/xml" + xml = resp.lxml + assert ( + xml.xpath( + "/ogc:ServiceExceptionReport/ogc:ServiceException/@code", + namespaces=ns130, + ) == + [] + ) + assert ( + "No response from URL" + in xml.xpath("//ogc:ServiceException/text()", namespaces=ns130)[0] + ) + assert validate_with_xsd(xml, xsd_name="wms/1.3.0/exceptions_1_3_0.xsd") + + def test_get_map(self, app, cache_dir): + with tmp_image((256, 256), format="jpeg") as img: + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg" + "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=" + "&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428" + "&WIDTH=256" + }, + {"body": img.read(), "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + self.common_map_req.params["bbox"] = "0,0,180,90" # internal axis-order + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" + + assert cache_dir.join( + "wms_cache_EPSG900913/01/000/000/001/000/000/001.jpeg" + ).check() + + def test_get_featureinfo(self, app): + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913" + "&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=foo,bar&I=10&J=20" + }, + {"body": b"info", "headers": {"content-type": "text/plain"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + resp = app.get(self.common_fi_req) + assert resp.content_type == "text/plain" + assert resp.body == b"info" + + def test_get_featureinfo_111(self, app): + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913" + "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=foo,bar&X=10&Y=20" + }, + {"body": b"info", "headers": {"content-type": "text/plain"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + self.common_fi_req.params["layers"] = "wms_cache" + self.common_fi_req.params["query_layers"] = "wms_cache" + resp = app.get(self.common_fi_req) + assert resp.content_type == "text/plain" + assert resp.body == b"info" + + +@pytest.mark.skipif(sys.platform == "win32", reason="not supported on Windows") +class TestWMSLinkSingleColorImages(SysTest): + config_file = "layer.yaml" + + def setup(self): + self.common_map_req = WMS111MapRequest( + url="/service?", + param=dict( + service="WMS", + version="1.1.1", + bbox="-180,0,0,80", + width="200", + height="200", + layers="wms_cache_link_single", + srs="EPSG:4326", + format="image/jpeg", + styles="", + request="GetMap", + ), + ) + + def test_get_map(self, app, cache_dir): + link_name = "wms_cache_link_single_EPSG900913/01/000/000/001/000/000/001.png" + real_name = "wms_cache_link_single_EPSG900913/single_color_tiles/fe00a0.png" + with tmp_image((256, 256), format="jpeg", color="#fe00a0") as img: + expected_req = ( + { + "path": r"/service?LAYERs=foo,bar&SERVICE=WMS&FORMAT=image%2Fjpeg" + "&REQUEST=GetMap&HEIGHT=256&SRS=EPSG%3A900913&styles=" + "&VERSION=1.1.1&BBOX=0.0,0.0,20037508.3428,20037508.3428" + "&WIDTH=256" + }, + {"body": img.read(), "headers": {"content-type": "image/jpeg"}}, + ) + with mock_httpd( + ("localhost", 42423), [expected_req], bbox_aware_query_comparator=True + ): + self.common_map_req.params["bbox"] = "0,0,180,90" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/jpeg" + + single_loc = cache_dir.join(real_name) + tile_loc = cache_dir.join(link_name) + assert single_loc.check() + assert tile_loc.check(link=True) + + self.common_map_req.params["format"] = "image/png" + resp = app.get(self.common_map_req) + assert resp.content_type == "image/png" + + +def assert_almost_equal_bbox(bbox1, bbox2, rel=0.01): + assert bbox1 == pytest.approx(bbox2, rel=rel) + + +def is_100_capa(xml): + return validate_with_dtd(xml, dtd_name="wms/1.0.0/capabilities_1_0_0.dtd") + + +def is_110_capa(xml): + return validate_with_dtd(xml, dtd_name="wms/1.1.0/capabilities_1_1_0.dtd") + +def is_111_exception(xml, msg=None, code=None, re_msg=None): + assert xml.xpath("/ServiceExceptionReport/@version")[0] == "1.1.1" + if msg: + assert xml.xpath("//ServiceException/text()")[0] == msg + if re_msg: + exception_msg = xml.xpath("//ServiceException/text()")[0] + assert re.findall(re_msg, exception_msg, re.I), "'%r' does not match '%s'" % ( + re_msg, + exception_msg, + ) + if code is not None: + assert xml.xpath("/ServiceExceptionReport/ServiceException/@code")[0] == code + assert validate_with_dtd(xml, "wms/1.1.1/exception_1_1_1.dtd") + + +def is_111_capa(xml): + return validate_with_dtd(xml, dtd_name="wms/1.1.1/WMS_MS_Capabilities.dtd") + + +def is_130_capa(xml): + return validate_with_xsd(xml, xsd_name="wms/1.3.0/capabilities_1_3_0.xsd") diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_wms_srs_extent.py mapproxy-1.12.0/mapproxy/test/system/test_wms_srs_extent.py --- mapproxy-1.11.0/mapproxy/test/system/test_wms_srs_extent.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_wms_srs_extent.py 2019-08-30 07:34:08.000000000 +0000 @@ -16,135 +16,148 @@ from __future__ import division from mapproxy.request.wms import WMS111MapRequest, WMS111CapabilitiesRequest -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config from mapproxy.test.image import is_png, is_transparent from mapproxy.test.image import tmp_image, assert_colors_equal, img_from_buf from mapproxy.test.http import mock_httpd +from mapproxy.test.system import SysTest from mapproxy.test.system.test_wms import bbox_srs_from_boundingbox -from mapproxy.test.unit.test_grid import assert_almost_equal_bbox -from nose.tools import eq_ -test_config = {} -base_config = make_base_config(test_config) +import pytest -def setup_module(): - module_setup(test_config, 'wms_srs_extent.yaml') -def teardown_module(): - module_teardown(test_config) +@pytest.fixture(scope="module") +def config_file(): + return "wms_srs_extent.yaml" -class TestWMSSRSExtentTest(SystemTest): - config = test_config + +class TestWMSSRSExtentTest(SysTest): def setup(self): - SystemTest.setup(self) - self.common_req = WMS111MapRequest(url='/service?', param=dict(service='WMS', - version='1.1.1')) - - def test_wms_capabilities(self): - req = WMS111CapabilitiesRequest(url='/service?').copy_with_request_params(self.common_req) - resp = self.app.get(req) - eq_(resp.content_type, 'application/vnd.ogc.wms_xml') + self.common_req = WMS111MapRequest( + url="/service?", param=dict(service="WMS", version="1.1.1") + ) + + def test_wms_capabilities(self, app): + req = WMS111CapabilitiesRequest(url="/service?").copy_with_request_params( + self.common_req + ) + resp = app.get(req) + assert resp.content_type == "application/vnd.ogc.wms_xml" xml = resp.lxml - bboxs = xml.xpath('//Layer/Layer[1]/BoundingBox') - bboxs = dict((e.attrib['SRS'], e) for e in bboxs) - - assert_almost_equal_bbox( - bbox_srs_from_boundingbox(bboxs['EPSG:31467']), - [2750000.0, 5000000.0, 4250000.0, 6500000.0]) - assert_almost_equal_bbox( - bbox_srs_from_boundingbox(bboxs['EPSG:25832']), - [0.0, 3500000.0, 1000000.0, 8500000.0]) - - assert_almost_equal_bbox( - bbox_srs_from_boundingbox(bboxs['EPSG:3857']), - [-20037508.3428, -147730762.670, 20037508.3428, 147730758.195]) - assert_almost_equal_bbox( - bbox_srs_from_boundingbox(bboxs['EPSG:4326']), - [-180.0, -90.0, 180.0, 90.0]) + bboxs = xml.xpath("//Layer/Layer[1]/BoundingBox") + bboxs = dict((e.attrib["SRS"], e) for e in bboxs) + assert bbox_srs_from_boundingbox(bboxs["EPSG:31467"]) == pytest.approx( + [2750000.0, 5000000.0, 4250000.0, 6500000.0] + ) + assert bbox_srs_from_boundingbox(bboxs["EPSG:25832"]) == pytest.approx( + [0.0, 3500000.0, 1000000.0, 8500000.0] + ) + + assert bbox_srs_from_boundingbox(bboxs["EPSG:3857"]) == pytest.approx( + [-20037508.3428, -147730762.670, 20037508.3428, 147730758.195] + ) + assert bbox_srs_from_boundingbox(bboxs["EPSG:4326"]) == pytest.approx( + [-180.0, -90.0, 180.0, 90.0] + ) # bboxes clipped to coverage - bboxs = xml.xpath('//Layer/Layer[2]/BoundingBox') - bboxs = dict((e.attrib['SRS'], e) for e in bboxs) - assert_almost_equal_bbox( - bbox_srs_from_boundingbox(bboxs['EPSG:31467']), - [3213331.57335, 5540436.91132, 3571769.72263, 6104110.432]) - assert_almost_equal_bbox( - bbox_srs_from_boundingbox(bboxs['EPSG:25832']), - [213372.048961, 5538660.64621, 571666.447504, 6102110.74547]) - - assert_almost_equal_bbox( - bbox_srs_from_boundingbox(bboxs['EPSG:3857']), - [556597.453966, 6446275.84102, 1113194.90793, 7361866.11305]) - assert_almost_equal_bbox( - bbox_srs_from_boundingbox(bboxs['EPSG:4326']), - [5.0, 50.0, 10.0, 55.0]) - - - - def test_out_of_extent(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetMap' - '&LAYERS=direct&STYLES=' - '&WIDTH=100&HEIGHT=100&FORMAT=image/png' - '&BBOX=-10000,0,0,1000&SRS=EPSG:25832' - '&VERSION=1.1.0&TRANSPARENT=TRUE') + bboxs = xml.xpath("//Layer/Layer[2]/BoundingBox") + bboxs = dict((e.attrib["SRS"], e) for e in bboxs) + assert bbox_srs_from_boundingbox(bboxs["EPSG:31467"]) == pytest.approx( + [3213331.57335, 5540436.91132, 3571769.72263, 6104110.432], + 3, # EPSG params changed with proj versions + ) + assert bbox_srs_from_boundingbox(bboxs["EPSG:25832"]) == pytest.approx( + [213372.048961, 5538660.64621, 571666.447504, 6102110.74547] + ) + + assert bbox_srs_from_boundingbox(bboxs["EPSG:3857"]) == pytest.approx( + [556597.453966, 6446275.84102, 1113194.90793, 7361866.11305] + ) + assert bbox_srs_from_boundingbox(bboxs["EPSG:4326"]) == pytest.approx( + [5.0, 50.0, 10.0, 55.0] + ) + + def test_out_of_extent(self, app): + resp = app.get( + "http://localhost/service?SERVICE=WMS&REQUEST=GetMap" + "&LAYERS=direct&STYLES=" + "&WIDTH=100&HEIGHT=100&FORMAT=image/png" + "&BBOX=-10000,0,0,1000&SRS=EPSG:25832" + "&VERSION=1.1.0&TRANSPARENT=TRUE" + ) # empty/transparent response - eq_(resp.content_type, 'image/png') + assert resp.content_type == "image/png" assert is_png(resp.body) assert is_transparent(resp.body) - def test_out_of_extent_bgcolor(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetMap' - '&LAYERS=direct&STYLES=' - '&WIDTH=100&HEIGHT=100&FORMAT=image/png' - '&BBOX=-10000,0,0,1000&SRS=EPSG:25832' - '&VERSION=1.1.0&TRANSPARENT=FALSE&BGCOLOR=0xff0000') + def test_out_of_extent_bgcolor(self, app): + resp = app.get( + "http://localhost/service?SERVICE=WMS&REQUEST=GetMap" + "&LAYERS=direct&STYLES=" + "&WIDTH=100&HEIGHT=100&FORMAT=image/png" + "&BBOX=-10000,0,0,1000&SRS=EPSG:25832" + "&VERSION=1.1.0&TRANSPARENT=FALSE&BGCOLOR=0xff0000" + ) # red response - eq_(resp.content_type, 'image/png') + assert resp.content_type == "image/png" assert is_png(resp.body) - assert_colors_equal(img_from_buf(resp.body).convert('RGBA'), - [(100 * 100, [255, 0, 0, 255])]) - - def test_clipped(self): - with tmp_image((256, 256), format='png', color=(255, 0, 0)) as img: - expected_req = ({'path': - r'/service?LAYERs=bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=100&SRS=EPSG%3A25832&styles=' - '&VERSION=1.1.1&BBOX=0.0,3500000.0,150.0,3500100.0' - '&WIDTH=75'}, - {'body': img.read(), 'headers': {'content-type': 'image/png'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetMap' - '&LAYERS=direct&STYLES=' - '&WIDTH=100&HEIGHT=100&FORMAT=image/png' - '&BBOX=-50,3500000,150,3500100&SRS=EPSG:25832' - '&VERSION=1.1.0&TRANSPARENT=TRUE') - eq_(resp.content_type, 'image/png') + assert_colors_equal( + img_from_buf(resp.body).convert("RGBA"), [(100 * 100, [255, 0, 0, 255])] + ) + + def test_clipped(self, app): + with tmp_image((256, 256), format="png", color=(255, 0, 0)) as img: + expected_req = ( + { + "path": r"/service?LAYERs=bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=100&SRS=EPSG%3A25832&styles=" + "&VERSION=1.1.1&BBOX=0.0,3500000.0,150.0,3500100.0" + "&WIDTH=75" + }, + {"body": img.read(), "headers": {"content-type": "image/png"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + resp = app.get( + "http://localhost/service?SERVICE=WMS&REQUEST=GetMap" + "&LAYERS=direct&STYLES=" + "&WIDTH=100&HEIGHT=100&FORMAT=image/png" + "&BBOX=-50,3500000,150,3500100&SRS=EPSG:25832" + "&VERSION=1.1.0&TRANSPARENT=TRUE" + ) + assert resp.content_type == "image/png" assert is_png(resp.body) - colors = sorted(img_from_buf(resp.body).convert('RGBA').getcolors()) + colors = sorted(img_from_buf(resp.body).convert("RGBA").getcolors()) # quarter is clipped, check if it's transparent - eq_(colors[0][0], (25 * 100)) - eq_(colors[0][1][3], 0) - eq_(colors[1], (75 * 100, (255, 0, 0, 255))) - - def test_clipped_bgcolor(self): - with tmp_image((256, 256), format='png', color=(255, 0, 0)) as img: - expected_req = ({'path': - r'/service?LAYERs=bar&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetMap&HEIGHT=100&SRS=EPSG%3A25832&styles=' - '&VERSION=1.1.1&BBOX=0.0,3500000.0,100.0,3500100.0' - '&WIDTH=50'}, - {'body': img.read(), 'headers': {'content-type': 'image/png'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetMap' - '&LAYERS=direct&STYLES=' - '&WIDTH=100&HEIGHT=100&FORMAT=image/png' - '&BBOX=-100,3500000,100,3500100&SRS=EPSG:25832' - '&VERSION=1.1.0&TRANSPARENT=FALSE&BGCOLOR=0x00ff00') - eq_(resp.content_type, 'image/png') + assert colors[0][0] == (25 * 100) + assert colors[0][1][3] == 0 + assert colors[1] == (75 * 100, (255, 0, 0, 255)) + + def test_clipped_bgcolor(self, app): + with tmp_image((256, 256), format="png", color=(255, 0, 0)) as img: + expected_req = ( + { + "path": r"/service?LAYERs=bar&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetMap&HEIGHT=100&SRS=EPSG%3A25832&styles=" + "&VERSION=1.1.1&BBOX=0.0,3500000.0,100.0,3500100.0" + "&WIDTH=50" + }, + {"body": img.read(), "headers": {"content-type": "image/png"}}, + ) + with mock_httpd(("localhost", 42423), [expected_req]): + resp = app.get( + "http://localhost/service?SERVICE=WMS&REQUEST=GetMap" + "&LAYERS=direct&STYLES=" + "&WIDTH=100&HEIGHT=100&FORMAT=image/png" + "&BBOX=-100,3500000,100,3500100&SRS=EPSG:25832" + "&VERSION=1.1.0&TRANSPARENT=FALSE&BGCOLOR=0x00ff00" + ) + assert resp.content_type == "image/png" assert is_png(resp.body) - assert_colors_equal(img_from_buf(resp.body).convert('RGBA'), - [(50 * 100, [255, 0, 0, 255]), (50 * 100, [0, 255, 0, 255])]) + assert_colors_equal( + img_from_buf(resp.body).convert("RGBA"), + [(50 * 100, [255, 0, 0, 255]), (50 * 100, [0, 255, 0, 255])], + ) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_wms_version.py mapproxy-1.12.0/mapproxy/test/system/test_wms_version.py --- mapproxy-1.11.0/mapproxy/test/system/test_wms_version.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_wms_version.py 2019-08-30 07:34:08.000000000 +0000 @@ -15,43 +15,71 @@ from __future__ import division -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config -from mapproxy.test.system.test_wms import is_110_capa, is_111_capa +from mapproxy.test.system import SysTest +from mapproxy.test.system.test_wms import is_110_capa, is_111_capa, is_130_capa +import pytest -test_config = {} -base_config = make_base_config(test_config) -def setup_module(): - module_setup(test_config, 'wms_versions.yaml') +class TestLimitedWMSVersionsTest(SysTest): -def teardown_module(): - module_teardown(test_config) + @pytest.fixture(scope="class") + def config_file(self): + return "wms_versions.yaml" -class TestWMSVersionsTest(SystemTest): - config = test_config + def test_default_version_130(self, app): + resp = app.get("http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities") + assert is_111_capa(resp.lxml) - def test_supported_version_110(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' - '&VERSION=1.1.0') + def test_supported_version_110(self, app): + resp = app.get( + "http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities" + "&VERSION=1.1.0" + ) assert is_110_capa(resp.lxml) - def test_unknown_version_113(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' - '&VERSION=1.1.3') + def test_unknown_version_113(self, app): + resp = app.get( + "http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities" + "&VERSION=1.1.3" + ) assert is_111_capa(resp.lxml) - def test_unknown_version_090(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' - '&WMTVER=0.9.0') + def test_unknown_version_090(self, app): + resp = app.get( + "http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities" + "&WMTVER=0.9.0" + ) assert is_110_capa(resp.lxml) - def test_unsupported_version_130(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' - '&VERSION=1.3.0') + def test_unsupported_version_130(self, app): + resp = app.get( + "http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities" + "&VERSION=1.3.0" + ) assert is_111_capa(resp.lxml) - def test_unknown_version_200(self): - resp = self.app.get('http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities' - '&VERSION=2.0.0') + def test_unknown_version_200(self, app): + resp = app.get( + "http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities" + "&VERSION=2.0.0" + ) assert is_111_capa(resp.lxml) + + +class TestWMSVersionsTest(SysTest): + + @pytest.fixture(scope="class") + def config_file(self): + return "layer.yaml" + + def test_default_version_130(self, app): + resp = app.get("http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities") + assert is_130_capa(resp.lxml) + + def test_unknown_version_200(self, app): + resp = app.get( + "http://localhost/service?SERVICE=WMS&REQUEST=GetCapabilities" + "&VERSION=2.0.0" + ) + assert is_130_capa(resp.lxml) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_wmts_dimensions.py mapproxy-1.12.0/mapproxy/test/system/test_wmts_dimensions.py --- mapproxy-1.11.0/mapproxy/test/system/test_wmts_dimensions.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_wmts_dimensions.py 2019-08-30 07:34:08.000000000 +0000 @@ -17,63 +17,76 @@ import functools +import pytest + from mapproxy.test.image import create_tmp_image from mapproxy.test.http import MockServ from mapproxy.test.helper import validate_with_xsd -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config -from nose.tools import eq_ +from mapproxy.test.system import SysTest -test_config = {} -base_config = make_base_config(test_config) -def setup_module(): - module_setup(test_config, 'wmts_dimensions.yaml', with_cache_data=True) +@pytest.fixture(scope="module") +def config_file(): + return "wmts_dimensions.yaml" -def teardown_module(): - module_teardown(test_config) ns_wmts = { - 'wmts': 'http://www.opengis.net/wmts/1.0', - 'ows': 'http://www.opengis.net/ows/1.1', - 'xlink': 'http://www.w3.org/1999/xlink' + "wmts": "http://www.opengis.net/wmts/1.0", + "ows": "http://www.opengis.net/ows/1.1", + "xlink": "http://www.w3.org/1999/xlink", } -def eq_xpath(xml, xpath, expected, namespaces=None): - eq_(xml.xpath(xpath, namespaces=namespaces)[0], expected) -eq_xpath_wmts = functools.partial(eq_xpath, namespaces=ns_wmts) +def assert_xpath(xml, xpath, expected, namespaces=None): + assert xml.xpath(xpath, namespaces=namespaces)[0] == expected + + +assert_xpath_wmts = functools.partial(assert_xpath, namespaces=ns_wmts) DIMENSION_LAYER_BASE_REQ = ( - '/service1?styles=&format=image%2Fpng&height=256' - '&bbox=-20037508.3428,0.0,0.0,20037508.3428' - '&layers=foo,bar&service=WMS&srs=EPSG%3A900913' - '&request=GetMap&width=256&version=1.1.1' + "/service1?styles=&format=image%2Fpng&height=256" + "&bbox=-20037508.3428,0.0,0.0,20037508.3428" + "&layers=foo,bar&service=WMS&srs=EPSG%3A900913" + "&request=GetMap&width=256&version=1.1.1" +) +NO_DIMENSION_LAYER_BASE_REQ = DIMENSION_LAYER_BASE_REQ.replace( + "/service1?", "/service2?" ) -NO_DIMENSION_LAYER_BASE_REQ = DIMENSION_LAYER_BASE_REQ.replace('/service1?', '/service2?') WMTS_KVP_URL = ( - '/service?service=wmts&request=GetTile&version=1.0.0' - '&tilematrixset=GLOBAL_MERCATOR&tilematrix=01&tilecol=0&tilerow=0&format=png&style=' + "/service?service=wmts&request=GetTile&version=1.0.0" + "&tilematrixset=GLOBAL_MERCATOR&tilematrix=01&tilecol=0&tilerow=0&format=png&style=" ) TEST_TILE = create_tmp_image((256, 256)) -class TestWMTS(SystemTest): - config = test_config - def setup(self): - SystemTest.setup(self) - def test_capabilities(self): - resp = self.app.get('/wmts/myrest/1.0.0/WMTSCapabilities.xml') +class TestWMTS(SysTest): + + def test_capabilities(self, app): + resp = app.get("/wmts/1.0.0/WMTSCapabilities.xml") xml = resp.lxml - assert validate_with_xsd(xml, xsd_name='wmts/1.0/wmtsGetCapabilities_response.xsd') + assert validate_with_xsd( + xml, xsd_name="wmts/1.0/wmtsGetCapabilities_response.xsd" + ) - eq_(len(xml.xpath('//wmts:Layer', namespaces=ns_wmts)), 2) - eq_(len(xml.xpath('//wmts:Contents/wmts:TileMatrixSet', namespaces=ns_wmts)), 1) + assert len(xml.xpath("//wmts:Layer", namespaces=ns_wmts)) == 2 + assert ( + len(xml.xpath("//wmts:Contents/wmts:TileMatrixSet", namespaces=ns_wmts)) + == 1 + ) - eq_(set(xml.xpath('//wmts:Contents/wmts:Layer/wmts:ResourceURL/@template', namespaces=ns_wmts)), - set(['http://localhost/wmts/myrest/dimension_layer/{TileMatrixSet}/{Time}/{Elevation}/{TileMatrix}/{TileCol}/{TileRow}.png', - 'http://localhost/wmts/myrest/no_dimension_layer/{TileMatrixSet}/{Time}/{Elevation}/{TileMatrix}/{TileCol}/{TileRow}.png'])) + assert set( + xml.xpath( + "//wmts:Contents/wmts:Layer/wmts:ResourceURL/@template", + namespaces=ns_wmts, + ) + ) == set( + [ + "http://localhost/wmts/dimension_layer/{TileMatrixSet}/{Time}/{Elevation}/{TileMatrix}/{TileCol}/{TileRow}.png", + "http://localhost/wmts/no_dimension_layer/{TileMatrixSet}/{Time}/{Elevation}/{TileMatrix}/{TileCol}/{TileRow}.png", + ] + ) # check dimension values for dimension_layer dimension_elems = xml.xpath( @@ -82,83 +95,112 @@ ) dimensions = {} for elem in dimension_elems: - dim = elem.find('{http://www.opengis.net/ows/1.1}Identifier').text - default = elem.find('{http://www.opengis.net/wmts/1.0}Default').text - values = [e.text for e in elem.findall('{http://www.opengis.net/wmts/1.0}Value')] + dim = elem.find("{http://www.opengis.net/ows/1.1}Identifier").text + default = elem.find("{http://www.opengis.net/wmts/1.0}Default").text + values = [ + e.text for e in elem.findall("{http://www.opengis.net/wmts/1.0}Value") + ] dimensions[dim] = (values, default) - eq_(dimensions['Time'][0], - ["2012-11-12T00:00:00", "2012-11-13T00:00:00", - "2012-11-14T00:00:00", "2012-11-15T00:00:00"] - ) - eq_(dimensions['Time'][1], '2012-11-15T00:00:00') - eq_(dimensions['Elevation'][1], '0') - eq_(dimensions['Elevation'][0], - ["0", "1000", "3000"] + assert dimensions["Time"][0] == [ + "2012-11-12T00:00:00", + "2012-11-13T00:00:00", + "2012-11-14T00:00:00", + "2012-11-15T00:00:00", + ] + assert dimensions["Time"][1] == "2012-11-15T00:00:00" + assert dimensions["Elevation"][1] == "0" + assert dimensions["Elevation"][0] == ["0", "1000", "3000"] + + def test_get_tile_valid_dimension(self, app): + serv = MockServ(42423, bbox_aware_query_comparator=True) + serv.expects( + DIMENSION_LAYER_BASE_REQ + "&Time=2012-11-15T00:00:00&elevation=1000" + ).returns(TEST_TILE) + with serv: + resp = app.get( + "/wmts/dimension_layer/GLOBAL_MERCATOR/2012-11-15T00:00:00/1000/01/0/0.png" + ) + assert resp.content_type == "image/png" + + def test_get_tile_invalid_dimension(self, app): + self.check_invalid_parameter( + app, + "/wmts/dimension_layer/GLOBAL_MERCATOR/2042-11-15T00:00:00/default/01/0/0.png", ) - - def test_get_tile_valid_dimension(self): - serv = MockServ(42423, bbox_aware_query_comparator=True) - serv.expects(DIMENSION_LAYER_BASE_REQ + '&Time=2012-11-15T00:00:00&elevation=1000').returns(TEST_TILE) - with serv: - resp = self.app.get('/wmts/dimension_layer/GLOBAL_MERCATOR/2012-11-15T00:00:00/1000/01/0/0.png') - eq_(resp.content_type, 'image/png') - - def test_get_tile_invalid_dimension(self): - self.check_invalid_parameter('/wmts/dimension_layer/GLOBAL_MERCATOR/2042-11-15T00:00:00/default/01/0/0.png') - - def test_get_tile_default_dimension(self): + def test_get_tile_default_dimension(self, app): serv = MockServ(42423, bbox_aware_query_comparator=True) - serv.expects(DIMENSION_LAYER_BASE_REQ + '&Time=2012-11-15T00:00:00&elevation=0').returns(TEST_TILE) - with serv: - resp = self.app.get('/wmts/dimension_layer/GLOBAL_MERCATOR/default/default/01/0/0.png') - eq_(resp.content_type, 'image/png') + serv.expects( + DIMENSION_LAYER_BASE_REQ + "&Time=2012-11-15T00:00:00&elevation=0" + ).returns(TEST_TILE) + with serv: + resp = app.get( + "/wmts/dimension_layer/GLOBAL_MERCATOR/default/default/01/0/0.png" + ) + assert resp.content_type == "image/png" - def test_get_tile_invalid_no_dimension_source(self): + def test_get_tile_invalid_no_dimension_source(self, app): # unsupported dimension need to be 'default' in RESTful request - self.check_invalid_parameter('/wmts/no_dimension_layer/GLOBAL_MERCATOR/2042-11-15T00:00:00/default/01/0/0.png') + self.check_invalid_parameter( + app, + "/wmts/no_dimension_layer/GLOBAL_MERCATOR/2042-11-15T00:00:00/default/01/0/0.png", + ) - def test_get_tile_default_no_dimension_source(self): + def test_get_tile_default_no_dimension_source(self, app): # check if dimensions are ignored serv = MockServ(42423, bbox_aware_query_comparator=True) serv.expects(NO_DIMENSION_LAYER_BASE_REQ).returns(TEST_TILE) with serv: - resp = self.app.get('/wmts/no_dimension_layer/GLOBAL_MERCATOR/default/default/01/0/0.png') - eq_(resp.content_type, 'image/png') - - - def test_get_tile_kvp_valid_dimension(self): - serv = MockServ(42423, bbox_aware_query_comparator=True) - serv.expects(DIMENSION_LAYER_BASE_REQ + '&Time=2012-11-14T00:00:00&elevation=3000').returns(TEST_TILE) - with serv: - resp = self.app.get(WMTS_KVP_URL + '&layer=dimension_layer&timE=2012-11-14T00:00:00&ELEvatioN=3000') - eq_(resp.content_type, 'image/png') - - def test_get_tile_kvp_valid_dimension_defaults(self): - serv = MockServ(42423, bbox_aware_query_comparator=True) - serv.expects(DIMENSION_LAYER_BASE_REQ + '&Time=2012-11-15T00:00:00&elevation=0').returns(TEST_TILE) - with serv: - resp = self.app.get(WMTS_KVP_URL + '&layer=dimension_layer') - eq_(resp.content_type, 'image/png') - - def test_get_tile_kvp_invalid_dimension(self): - self.check_invalid_parameter(WMTS_KVP_URL + '&layer=dimension_layer&elevation=500') - + resp = app.get( + "/wmts/no_dimension_layer/GLOBAL_MERCATOR/default/default/01/0/0.png" + ) + assert resp.content_type == "image/png" + + def test_get_tile_kvp_valid_dimension(self, app): + serv = MockServ(42423, bbox_aware_query_comparator=True) + serv.expects( + DIMENSION_LAYER_BASE_REQ + "&Time=2012-11-14T00:00:00&elevation=3000" + ).returns(TEST_TILE) + with serv: + resp = app.get( + WMTS_KVP_URL + + "&layer=dimension_layer&timE=2012-11-14T00:00:00&ELEvatioN=3000" + ) + assert resp.content_type == "image/png" + + def test_get_tile_kvp_valid_dimension_defaults(self, app): + serv = MockServ(42423, bbox_aware_query_comparator=True) + serv.expects( + DIMENSION_LAYER_BASE_REQ + "&Time=2012-11-15T00:00:00&elevation=0" + ).returns(TEST_TILE) + with serv: + resp = app.get(WMTS_KVP_URL + "&layer=dimension_layer") + assert resp.content_type == "image/png" + + def test_get_tile_kvp_invalid_dimension(self, app): + self.check_invalid_parameter( + app, WMTS_KVP_URL + "&layer=dimension_layer&elevation=500" + ) - def test_get_tile_kvp_default_no_dimension_source(self): + def test_get_tile_kvp_default_no_dimension_source(self, app): # check if dimensions are ignored serv = MockServ(42423, bbox_aware_query_comparator=True) serv.expects(NO_DIMENSION_LAYER_BASE_REQ).returns(TEST_TILE) with serv: - resp = self.app.get(WMTS_KVP_URL + '&layer=no_dimension_layer&Time=2012-11-14T00:00:00&Elevation=3000') - eq_(resp.content_type, 'image/png') + resp = app.get( + WMTS_KVP_URL + + "&layer=no_dimension_layer&Time=2012-11-14T00:00:00&Elevation=3000" + ) + assert resp.content_type == "image/png" - def check_invalid_parameter(self, url): - resp = self.app.get(url, status=400) + def check_invalid_parameter(self, app, url): + resp = app.get(url, status=400) xml = resp.lxml - eq_(resp.content_type, 'text/xml') - assert validate_with_xsd(xml, xsd_name='ows/1.1.0/owsExceptionReport.xsd') - eq_xpath_wmts(xml, '/ows:ExceptionReport/ows:Exception/@exceptionCode', - 'InvalidParameterValue') - + assert resp.content_type == "text/xml" + assert validate_with_xsd(xml, xsd_name="ows/1.1.0/owsExceptionReport.xsd") + assert_xpath_wmts( + xml, + "/ows:ExceptionReport/ows:Exception/@exceptionCode", + "InvalidParameterValue", + ) diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_wmts.py mapproxy-1.12.0/mapproxy/test/system/test_wmts.py --- mapproxy-1.11.0/mapproxy/test/system/test_wmts.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_wmts.py 2019-08-30 07:34:08.000000000 +0000 @@ -17,151 +17,318 @@ from __future__ import division import re -import os -import shutil import functools from io import BytesIO + +import pytest + from mapproxy.request.wmts import ( - WMTS100TileRequest, WMTS100CapabilitiesRequest + WMTS100TileRequest, + WMTS100FeatureInfoRequest, + WMTS100CapabilitiesRequest, ) from mapproxy.test.image import is_jpeg, create_tmp_image from mapproxy.test.http import MockServ from mapproxy.test.helper import validate_with_xsd -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config -from nose.tools import eq_ +from mapproxy.test.system import SysTest -test_config = {} -base_config = make_base_config(test_config) -def setup_module(): - module_setup(test_config, 'wmts.yaml', with_cache_data=True) +@pytest.fixture(scope="module") +def config_file(): + return "wmts.yaml" -def teardown_module(): - module_teardown(test_config) ns_wmts = { - 'wmts': 'http://www.opengis.net/wmts/1.0', - 'ows': 'http://www.opengis.net/ows/1.1', - 'xlink': 'http://www.w3.org/1999/xlink' + "wmts": "http://www.opengis.net/wmts/1.0", + "ows": "http://www.opengis.net/ows/1.1", + "xlink": "http://www.w3.org/1999/xlink", } -def eq_xpath(xml, xpath, expected, namespaces=None): - eq_(xml.xpath(xpath, namespaces=namespaces)[0], expected) -eq_xpath_wmts = functools.partial(eq_xpath, namespaces=ns_wmts) +def assert_xpath(xml, xpath, expected, namespaces=None): + assert xml.xpath(xpath, namespaces=namespaces)[0] == expected + + +assert_xpath_wmts = functools.partial(assert_xpath, namespaces=ns_wmts) + +@pytest.fixture +def fi_req(): + return WMTS100FeatureInfoRequest( + url="/service?", + param=dict( + service="WMTS", + version="1.0.0", + tilerow="0", + tilecol="0", + tilematrix="00", + tilematrixset="GLOBAL_MERCATOR", + layer="wms_cache", + format="image/png", + infoformat="application/json", + style="", + request="GetFeatureInfo", + i="17", + j="27" + ), + ) + +@pytest.fixture +def fi_req_with_featurecount(): + return WMTS100FeatureInfoRequest( + url="/service?", + param=dict( + service="WMTS", + version="1.0.0", + tilerow="0", + tilecol="0", + tilematrix="00", + tilematrixset="GLOBAL_MERCATOR", + layer="wms_cache", + format="image/png", + infoformat="application/json", + style="", + request="GetFeatureInfo", + i="17", + j="27", + feature_count="5" + ), + ) + + +class TestWMTS(SysTest): -class TestWMTS(SystemTest): - config = test_config def setup(self): - SystemTest.setup(self) - self.common_cap_req = WMTS100CapabilitiesRequest(url='/service?', param=dict(service='WMTS', - version='1.0.0', request='GetCapabilities')) - self.common_tile_req = WMTS100TileRequest(url='/service?', param=dict(service='WMTS', - version='1.0.0', tilerow='0', tilecol='0', tilematrix='01', tilematrixset='GLOBAL_MERCATOR', - layer='wms_cache', format='image/jpeg', style='', request='GetTile')) - - def test_endpoints(self): - for endpoint in ('service', 'ows'): - req = WMTS100CapabilitiesRequest(url='/%s?' % endpoint).copy_with_request_params(self.common_cap_req) - resp = self.app.get(req) - eq_(resp.content_type, 'application/xml') + self.common_cap_req = WMTS100CapabilitiesRequest( + url="/service?", + param=dict(service="WMTS", version="1.0.0", request="GetCapabilities"), + ) + self.common_tile_req = WMTS100TileRequest( + url="/service?", + param=dict( + service="WMTS", + version="1.0.0", + tilerow="0", + tilecol="0", + tilematrix="01", + tilematrixset="GLOBAL_MERCATOR", + layer="wms_cache", + format="image/jpeg", + style="", + request="GetTile", + ), + ) + + def test_endpoints(self, app): + for endpoint in ("service", "ows"): + req = WMTS100CapabilitiesRequest( + url="/%s?" % endpoint + ).copy_with_request_params(self.common_cap_req) + resp = app.get(req) + assert resp.content_type == "application/xml" xml = resp.lxml - assert validate_with_xsd(xml, xsd_name='wmts/1.0/wmtsGetCapabilities_response.xsd') + assert validate_with_xsd( + xml, xsd_name="wmts/1.0/wmtsGetCapabilities_response.xsd" + ) - def test_capabilities(self): + def test_capabilities(self, app): req = str(self.common_cap_req) - resp = self.app.get(req) - eq_(resp.content_type, 'application/xml') + resp = app.get(req) + assert resp.content_type == "application/xml" xml = resp.lxml - assert validate_with_xsd(xml, xsd_name='wmts/1.0/wmtsGetCapabilities_response.xsd') - eq_(xml.xpath('//wmts:Layer/ows:Identifier/text()', namespaces=ns_wmts), - ['wms_cache','wms_cache_multi','tms_cache','tms_cache_ul','gk3_cache'], + assert validate_with_xsd( + xml, xsd_name="wmts/1.0/wmtsGetCapabilities_response.xsd" + ) + assert xml.xpath("//wmts:Layer/ows:Identifier/text()", namespaces=ns_wmts) == [ + "wms_cache", + "wms_cache_multi", + "tms_cache", + "tms_cache_ul", + "gk3_cache", + ] + assert ( + len(xml.xpath("//wmts:Contents/wmts:TileMatrixSet", namespaces=ns_wmts)) + == 5 ) - eq_(len(xml.xpath('//wmts:Contents/wmts:TileMatrixSet', namespaces=ns_wmts)), 5) - goog_matrixset = xml.xpath('//wmts:Contents/wmts:TileMatrixSet[./ows:Identifier/text()="GoogleMapsCompatible"]', namespaces=ns_wmts)[0] - eq_(goog_matrixset.findtext('ows:Identifier', namespaces=ns_wmts), 'GoogleMapsCompatible') + # check InfoFormat for queryable layers + for layer in xml.xpath("//wmts:Layer", namespaces=ns_wmts): + if layer.findtext("ows:Identifier", namespaces=ns_wmts) in ( + "wms_cache", + "wms_cache_multi", + "gk3_cache", + "tms_cache", + ): + assert layer.xpath("wmts:InfoFormat/text()", namespaces=ns_wmts) == [ + "application/gml+xml; version=3.1", + "application/json", + ] + else: + assert layer.xpath("wmts:InfoFormat/text()", namespaces=ns_wmts) == [] + + goog_matrixset = xml.xpath( + '//wmts:Contents/wmts:TileMatrixSet[./ows:Identifier/text()="GoogleMapsCompatible"]', + namespaces=ns_wmts, + )[0] + assert ( + goog_matrixset.findtext("ows:Identifier", namespaces=ns_wmts) + == "GoogleMapsCompatible" + ) # top left corner: min X first then max Y - assert re.match('-20037508\.\d+ 20037508\.\d+', goog_matrixset.findtext('./wmts:TileMatrix[1]/wmts:TopLeftCorner', namespaces=ns_wmts)) + assert re.match( + r"-20037508\.\d+ 20037508\.\d+", + goog_matrixset.findtext( + "./wmts:TileMatrix[1]/wmts:TopLeftCorner", namespaces=ns_wmts + ), + ) - gk_matrixset = xml.xpath('//wmts:Contents/wmts:TileMatrixSet[./ows:Identifier/text()="gk3"]', namespaces=ns_wmts)[0] - eq_(gk_matrixset.findtext('ows:Identifier', namespaces=ns_wmts), 'gk3') + gk_matrixset = xml.xpath( + '//wmts:Contents/wmts:TileMatrixSet[./ows:Identifier/text()="gk3"]', + namespaces=ns_wmts, + )[0] + assert gk_matrixset.findtext("ows:Identifier", namespaces=ns_wmts) == "gk3" # Gauß-Krüger uses "reverse" axis order -> top left corner: max Y first then min X - assert re.match('6000000.0+ 3000000.0+', gk_matrixset.findtext('./wmts:TileMatrix[1]/wmts:TopLeftCorner', namespaces=ns_wmts)) + assert re.match( + "6000000.0+ 3000000.0+", + gk_matrixset.findtext( + "./wmts:TileMatrix[1]/wmts:TopLeftCorner", namespaces=ns_wmts + ), + ) - def test_get_tile(self): - resp = self.app.get(str(self.common_tile_req)) - eq_(resp.content_type, 'image/jpeg') + def test_get_tile(self, app, fixture_cache_data): + resp = app.get(str(self.common_tile_req)) + assert resp.content_type == "image/jpeg" data = BytesIO(resp.body) assert is_jpeg(data) # test with integer tilematrix - url = str(self.common_tile_req).replace('=01', '=1') - resp = self.app.get(url) - eq_(resp.content_type, 'image/jpeg') + url = str(self.common_tile_req).replace("=01", "=1") + resp = app.get(url) + assert resp.content_type == "image/jpeg" data = BytesIO(resp.body) assert is_jpeg(data) - def test_get_tile_flipped_axis(self): + def test_get_tile_flipped_axis(self, app, cache_dir, fixture_cache_data): # test default tile lock directory - tiles_lock_dir = os.path.join(test_config['base_dir'], 'cache_data', 'tile_locks') - # make sure default tile_lock_dir was not created by other tests - shutil.rmtree(tiles_lock_dir, ignore_errors=True) - assert not os.path.exists(tiles_lock_dir) - - self.common_tile_req.params['layer'] = 'tms_cache_ul' - self.common_tile_req.params['tilematrixset'] = 'ulgrid' - self.common_tile_req.params['format'] = 'image/png' - self.common_tile_req.tile = (0, 0, '01') + tiles_lock_dir = cache_dir.join("tile_locks") + assert not tiles_lock_dir.check() + + self.common_tile_req.params["layer"] = "tms_cache_ul" + self.common_tile_req.params["tilematrixset"] = "ulgrid" + self.common_tile_req.params["format"] = "image/png" + self.common_tile_req.tile = (0, 0, "01") serv = MockServ(port=42423) # source is ll, cache/service ul - serv.expects('/tiles/01/000/000/000/000/000/001.png') + serv.expects("/tiles/01/000/000/000/000/000/001.png") serv.returns(create_tmp_image((256, 256))) with serv: - resp = self.app.get(str(self.common_tile_req), status=200) - eq_(resp.content_type, 'image/png') + resp = app.get(str(self.common_tile_req), status=200) + assert resp.content_type == "image/png" # test default tile lock directory was created - assert os.path.exists(tiles_lock_dir) + assert tiles_lock_dir.check() - - def test_get_tile_source_error(self): - self.common_tile_req.params['layer'] = 'tms_cache' - self.common_tile_req.params['format'] = 'image/png' - resp = self.app.get(str(self.common_tile_req), status=500) + def test_get_tile_source_error(self, app): + self.common_tile_req.params["layer"] = "tms_cache" + self.common_tile_req.params["format"] = "image/png" + resp = app.get(str(self.common_tile_req), status=500) xml = resp.lxml - assert validate_with_xsd(xml, xsd_name='ows/1.1.0/owsExceptionReport.xsd') - eq_xpath_wmts(xml, '/ows:ExceptionReport/ows:Exception/@exceptionCode', - 'NoApplicableCode') + assert validate_with_xsd(xml, xsd_name="ows/1.1.0/owsExceptionReport.xsd") + assert_xpath_wmts( + xml, "/ows:ExceptionReport/ows:Exception/@exceptionCode", "NoApplicableCode" + ) - def test_get_tile_out_of_range(self): + def test_get_tile_out_of_range(self, app): self.common_tile_req.params.coord = -1, 1, 1 - resp = self.app.get(str(self.common_tile_req), status=400) + resp = app.get(str(self.common_tile_req), status=400) xml = resp.lxml - eq_(resp.content_type, 'text/xml') - assert validate_with_xsd(xml, xsd_name='ows/1.1.0/owsExceptionReport.xsd') - eq_xpath_wmts(xml, '/ows:ExceptionReport/ows:Exception/@exceptionCode', - 'TileOutOfRange') - - def test_get_tile_invalid_format(self): - self.common_tile_req.params['format'] = 'image/png' - self.check_invalid_parameter() - - def test_get_tile_invalid_layer(self): - self.common_tile_req.params['layer'] = 'unknown' - self.check_invalid_parameter() - - def test_get_tile_invalid_matrixset(self): - self.common_tile_req.params['tilematrixset'] = 'unknown' - self.check_invalid_parameter() + assert resp.content_type == "text/xml" + assert validate_with_xsd(xml, xsd_name="ows/1.1.0/owsExceptionReport.xsd") + assert_xpath_wmts( + xml, "/ows:ExceptionReport/ows:Exception/@exceptionCode", "TileOutOfRange" + ) + + def test_get_tile_invalid_format(self, app): + self.common_tile_req.params["format"] = "image/png" + self.check_invalid_parameter(app) + + def test_get_tile_invalid_layer(self, app): + self.common_tile_req.params["layer"] = "unknown" + self.check_invalid_parameter(app) + + def test_get_tile_invalid_matrixset(self, app): + self.common_tile_req.params["tilematrixset"] = "unknown" + self.check_invalid_parameter(app) - def check_invalid_parameter(self): - resp = self.app.get(str(self.common_tile_req), status=400) + def check_invalid_parameter(self, app): + resp = app.get(str(self.common_tile_req), status=400) xml = resp.lxml - eq_(resp.content_type, 'text/xml') - assert validate_with_xsd(xml, xsd_name='ows/1.1.0/owsExceptionReport.xsd') - eq_xpath_wmts(xml, '/ows:ExceptionReport/ows:Exception/@exceptionCode', - 'InvalidParameterValue') + assert resp.content_type == "text/xml" + assert validate_with_xsd(xml, xsd_name="ows/1.1.0/owsExceptionReport.xsd") + assert_xpath_wmts( + xml, + "/ows:ExceptionReport/ows:Exception/@exceptionCode", + "InvalidParameterValue", + ) + + def test_getfeatureinfo(self, app, fi_req): + serv = MockServ(port=42423) + serv.expects( + "/service?layers=foo,bar" + + "&bbox=-20037508.342789244,-20037508.342789244,20037508.342789244,20037508.342789244" + + "&width=256&height=256&x=17&y=27&query_layers=foo,bar&format=image%2Fpng&srs=EPSG%3A900913" + + "&request=GetFeatureInfo&version=1.1.1&service=WMS&styles=&info_format=application/json" + ) + serv.returns(b'{"data": 43}') + with serv: + resp = app.get(str(fi_req), status=200) + assert resp.content_type == "application/json" + + + def test_getfeatureinfo_coverage(self, app, fi_req): + fi_req.params['layer'] = 'tms_cache' + fi_req.params['i'] = '250' + fi_req.params['j'] = '50' + resp = app.get(str(fi_req), status=200) + assert resp.content_type == "application/json" + + fi_req.params['i'] = '150' + serv = MockServ(port=42423) + serv.expects( + "/service?layers=fi" + + "&bbox=-20037508.3428,-20037508.3428,20037508.3428,20037508.3428" + + "&width=256&height=256&x=150&y=50&query_layers=fi&format=image%2Fpng&srs=EPSG%3A900913" + + "&request=GetFeatureInfo&version=1.1.1&service=WMS&styles=&info_format=application/json" + ) + serv.returns(b'{"data": 43}') + with serv: + resp = app.get(str(fi_req), status=200) + assert resp.content_type == "application/json" + def test_getfeatureinfo_featurecount(self, app, fi_req_with_featurecount): + serv = MockServ(port=42423) + serv.expects( + "/service?layers=foo,bar" + + "&bbox=-20037508.342789244,-20037508.342789244,20037508.342789244,20037508.342789244" + + "&width=256&height=256&x=17&y=27&query_layers=foo,bar&format=image%2Fpng&srs=EPSG%3A900913" + + "&request=GetFeatureInfo&version=1.1.1&service=WMS&styles=&info_format=application/json&feature_count=5" + ) + serv.returns(b'{"data": 43}') + with serv: + resp = app.get(str(fi_req_with_featurecount), status=200) + assert resp.content_type == "application/json" + + def test_getfeatureinfo_xml(self, app, fi_req): + fi_req.params["infoformat"] = "application/gml+xml; version=3.1" + serv = MockServ(port=42423) + serv.expects( + "/service?layers=foo,bar" + + "&bbox=-20037508.342789244,-20037508.342789244,20037508.342789244,20037508.342789244" + + "&width=256&height=256&x=17&y=27&query_layers=foo,bar&format=image%2Fpng&srs=EPSG%3A900913" + + "&request=GetFeatureInfo&version=1.1.1&service=WMS&styles=&info_format=application/gml%2bxml%3b%20version=3.1" + ) + serv.returns(b"") + with serv: + resp = app.get(str(fi_req), status=200) + assert resp.headers["Content-type"] == "application/gml+xml; version=3.1" diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_wmts_restful.py mapproxy-1.12.0/mapproxy/test/system/test_wmts_restful.py --- mapproxy-1.11.0/mapproxy/test/system/test_wmts_restful.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_wmts_restful.py 2019-08-30 07:34:08.000000000 +0000 @@ -18,100 +18,181 @@ import functools from io import BytesIO + +import pytest + from mapproxy.test.image import is_jpeg, create_tmp_image from mapproxy.test.http import MockServ from mapproxy.test.helper import validate_with_xsd -from mapproxy.test.system import module_setup, module_teardown, SystemTest, make_base_config -from nose.tools import eq_ +from mapproxy.test.system import SysTest -test_config = {} -base_config = make_base_config(test_config) -def setup_module(): - module_setup(test_config, 'wmts.yaml', with_cache_data=True) +@pytest.fixture(scope="module") +def config_file(): + return "wmts.yaml" -def teardown_module(): - module_teardown(test_config) ns_wmts = { - 'wmts': 'http://www.opengis.net/wmts/1.0', - 'ows': 'http://www.opengis.net/ows/1.1', - 'xlink': 'http://www.w3.org/1999/xlink' + "wmts": "http://www.opengis.net/wmts/1.0", + "ows": "http://www.opengis.net/ows/1.1", + "xlink": "http://www.w3.org/1999/xlink", } -def eq_xpath(xml, xpath, expected, namespaces=None): - eq_(xml.xpath(xpath, namespaces=namespaces)[0], expected) -eq_xpath_wmts = functools.partial(eq_xpath, namespaces=ns_wmts) +def assert_xpath(xml, xpath, expected, namespaces=None): + assert xml.xpath(xpath, namespaces=namespaces)[0] == expected + +assert_xpath_wmts = functools.partial(assert_xpath, namespaces=ns_wmts) -class TestWMTS(SystemTest): - config = test_config - def setup(self): - SystemTest.setup(self) - def test_capabilities(self): - resp = self.app.get('/wmts/myrest/1.0.0/WMTSCapabilities.xml') +class TestWMTS(SysTest): + + def test_capabilities(self, app): + resp = app.get("/wmts/1.0.0/WMTSCapabilities.xml") xml = resp.lxml - assert validate_with_xsd(xml, xsd_name='wmts/1.0/wmtsGetCapabilities_response.xsd') - eq_(len(xml.xpath('//wmts:Layer', namespaces=ns_wmts)), 5) - eq_(len(xml.xpath('//wmts:Contents/wmts:TileMatrixSet', namespaces=ns_wmts)), 5) - - def test_get_tile(self): - resp = self.app.get('/wmts/myrest/wms_cache/GLOBAL_MERCATOR/01/0/0.jpeg') - eq_(resp.content_type, 'image/jpeg') + assert validate_with_xsd( + xml, xsd_name="wmts/1.0/wmtsGetCapabilities_response.xsd" + ) + assert len(xml.xpath("//wmts:Layer", namespaces=ns_wmts)) == 5 + assert ( + len(xml.xpath("//wmts:Contents/wmts:TileMatrixSet", namespaces=ns_wmts)) + == 5 + ) + # check InfoFormat for queryable layers + for layer in xml.xpath("//wmts:Layer", namespaces=ns_wmts): + if layer.findtext("ows:Identifier", namespaces=ns_wmts) in ( + "wms_cache", + "wms_cache_multi", + "gk3_cache", + "tms_cache", + ): + assert layer.xpath("wmts:InfoFormat/text()", namespaces=ns_wmts) == [ + "application/gml+xml; version=3.1", + "application/json", + ] + else: + assert layer.xpath("wmts:InfoFormat/text()", namespaces=ns_wmts) == [] + + # check ResourceURL for wms_cache + layer = xml.xpath( + '//wmts:Layer[ows:Identifier/text()="wms_cache"]', namespaces=ns_wmts + )[0] + resourceURLs = layer.xpath("wmts:ResourceURL", namespaces=ns_wmts) + + for rurl, format, type, template in [ + [ + resourceURLs[0], + "image/jpeg", + "tile", + "http://localhost/wmts/myrest/wms_cache/{TileMatrixSet}/{TileMatrix}/{TileCol}/{TileRow}.jpeg", + ], + [ + resourceURLs[1], + "application/gml+xml; version=3.1", + "FeatureInfo", + "http://localhost/wmts/myrest/wms_cache/{TileMatrixSet}/{TileMatrix}/{TileCol}/{TileRow}/{I}/{J}.gml", + ], + [ + resourceURLs[2], + "application/json", + "FeatureInfo", + "http://localhost/wmts/myrest/wms_cache/{TileMatrixSet}/{TileMatrix}/{TileCol}/{TileRow}/{I}/{J}.geojson", + ], + ]: + assert rurl.attrib["format"] == format + assert rurl.attrib["resourceType"] == type + assert rurl.attrib["template"] == template + + def test_get_tile(self, app, fixture_cache_data): + resp = app.get("/wmts/myrest/wms_cache/GLOBAL_MERCATOR/01/0/0.jpeg") + assert resp.content_type == "image/jpeg" data = BytesIO(resp.body) assert is_jpeg(data) # test without leading 0 in level - resp = self.app.get('/wmts/myrest/wms_cache/GLOBAL_MERCATOR/1/0/0.jpeg') - eq_(resp.content_type, 'image/jpeg') + resp = app.get("/wmts/myrest/wms_cache/GLOBAL_MERCATOR/1/0/0.jpeg") + assert resp.content_type == "image/jpeg" data = BytesIO(resp.body) assert is_jpeg(data) - def test_get_tile_flipped_axis(self): + def test_get_tile_flipped_axis(self, app): serv = MockServ(port=42423) # source is ll, cache/service ul - serv.expects('/tiles/01/000/000/000/000/000/001.png') + serv.expects("/tiles/01/000/000/000/000/000/001.png") serv.returns(create_tmp_image((256, 256))) with serv: - resp = self.app.get('/wmts/myrest/tms_cache_ul/ulgrid/01/0/0.png', status=200) - eq_(resp.content_type, 'image/png') + resp = app.get("/wmts/myrest/tms_cache_ul/ulgrid/01/0/0.png", status=200) + assert resp.content_type == "image/png" # test without leading 0 in level - resp = self.app.get('/wmts/myrest/tms_cache_ul/ulgrid/1/0/0.png', status=200) - eq_(resp.content_type, 'image/png') + resp = app.get("/wmts/myrest/tms_cache_ul/ulgrid/1/0/0.png", status=200) + assert resp.content_type == "image/png" - def test_get_tile_source_error(self): - resp = self.app.get('/wmts/myrest/tms_cache/GLOBAL_MERCATOR/01/0/0.png', status=500) + def test_get_tile_source_error(self, app): + resp = app.get("/wmts/myrest/tms_cache/GLOBAL_MERCATOR/01/0/0.png", status=500) xml = resp.lxml - assert validate_with_xsd(xml, xsd_name='ows/1.1.0/owsExceptionReport.xsd') - eq_xpath_wmts(xml, '/ows:ExceptionReport/ows:Exception/@exceptionCode', - 'NoApplicableCode') - - def test_get_tile_out_of_range(self): - resp = self.app.get('/wmts/myrest/wms_cache/GLOBAL_MERCATOR/01/-1/0.jpeg', status=400) + assert validate_with_xsd(xml, xsd_name="ows/1.1.0/owsExceptionReport.xsd") + assert_xpath_wmts( + xml, "/ows:ExceptionReport/ows:Exception/@exceptionCode", "NoApplicableCode" + ) + + def test_get_tile_out_of_range(self, app): + resp = app.get( + "/wmts/myrest/wms_cache/GLOBAL_MERCATOR/01/-1/0.jpeg", status=400 + ) xml = resp.lxml - eq_(resp.content_type, 'text/xml') - assert validate_with_xsd(xml, xsd_name='ows/1.1.0/owsExceptionReport.xsd') - eq_xpath_wmts(xml, '/ows:ExceptionReport/ows:Exception/@exceptionCode', - 'TileOutOfRange') - - def test_get_tile_invalid_format(self): - url = '/wmts/myrest/wms_cache/GLOBAL_MERCATOR/01/0/0.png' - self.check_invalid_parameter(url) - - def test_get_tile_invalid_layer(self): - url = '/wmts/myrest/unknown/GLOBAL_MERCATOR/01/0/0.jpeg' - self.check_invalid_parameter(url) - - def test_get_tile_invalid_matrixset(self): - url = '/wmts/myrest/wms_cache/unknown/01/0/0.jpeg' - self.check_invalid_parameter(url) + assert resp.content_type == "text/xml" + assert validate_with_xsd(xml, xsd_name="ows/1.1.0/owsExceptionReport.xsd") + assert_xpath_wmts( + xml, "/ows:ExceptionReport/ows:Exception/@exceptionCode", "TileOutOfRange" + ) + + def test_get_tile_invalid_format(self, app): + url = "/wmts/myrest/wms_cache/GLOBAL_MERCATOR/01/0/0.png" + self.check_invalid_parameter(app, url) + + def test_get_tile_invalid_layer(self, app): + url = "/wmts/myrest/unknown/GLOBAL_MERCATOR/01/0/0.jpeg" + self.check_invalid_parameter(app, url) + + def test_get_tile_invalid_matrixset(self, app): + url = "/wmts/myrest/wms_cache/unknown/01/0/0.jpeg" + self.check_invalid_parameter(app, url) - def check_invalid_parameter(self, url): - resp = self.app.get(url, status=400) + def check_invalid_parameter(self, app, url): + resp = app.get(url, status=400) xml = resp.lxml - eq_(resp.content_type, 'text/xml') - assert validate_with_xsd(xml, xsd_name='ows/1.1.0/owsExceptionReport.xsd') - eq_xpath_wmts(xml, '/ows:ExceptionReport/ows:Exception/@exceptionCode', - 'InvalidParameterValue') + assert resp.content_type == "text/xml" + assert validate_with_xsd(xml, xsd_name="ows/1.1.0/owsExceptionReport.xsd") + assert_xpath_wmts( + xml, + "/ows:ExceptionReport/ows:Exception/@exceptionCode", + "InvalidParameterValue", + ) + def test_getfeatureinfo(self, app): + fi_req = '/wmts/myrest/wms_cache/GLOBAL_MERCATOR/00/0/0/17/27.geojson' + serv = MockServ(port=42423) + serv.expects( + "/service?layers=foo,bar" + + "&bbox=-20037508.342789244,-20037508.342789244,20037508.342789244,20037508.342789244" + + "&width=256&height=256&x=17&y=27&query_layers=foo,bar&format=image%2Fpng&srs=EPSG%3A900913" + + "&request=GetFeatureInfo&version=1.1.1&service=WMS&styles=&info_format=application/json" + ) + serv.returns(b'{"data": 43}') + with serv: + resp = app.get(fi_req, status=200) + assert resp.content_type == "application/json" + + def test_getfeatureinfo_xml(self, app): + fi_req = '/wmts/myrest/wms_cache/GLOBAL_MERCATOR/00/0/0/17/27.gml' + serv = MockServ(port=42423) + serv.expects( + "/service?layers=foo,bar" + + "&bbox=-20037508.342789244,-20037508.342789244,20037508.342789244,20037508.342789244" + + "&width=256&height=256&x=17&y=27&query_layers=foo,bar&format=image%2Fpng&srs=EPSG%3A900913" + + "&request=GetFeatureInfo&version=1.1.1&service=WMS&styles=&info_format=application/gml%2bxml%3b%20version=3.1" + ) + serv.returns(b"") + with serv: + resp = app.get(str(fi_req), status=200) + assert resp.headers["Content-type"] == "application/gml+xml; version=3.1" diff -Nru mapproxy-1.11.0/mapproxy/test/system/test_xslt_featureinfo.py mapproxy-1.12.0/mapproxy/test/system/test_xslt_featureinfo.py --- mapproxy-1.11.0/mapproxy/test/system/test_xslt_featureinfo.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/system/test_xslt_featureinfo.py 2019-08-30 07:34:08.000000000 +0000 @@ -14,19 +14,17 @@ # limitations under the License. from __future__ import division -import os + +import pytest from mapproxy.request.wms import WMS111FeatureInfoRequest, WMS130FeatureInfoRequest -from mapproxy.test.system import module_setup, module_teardown, SystemTest +from mapproxy.test.system import SysTest from mapproxy.test.http import mock_httpd from mapproxy.test.helper import strip_whitespace -from nose.tools import eq_ - -test_config = {} - -xslt_input = b""" +xslt_input = ( + b""" @@ -35,8 +33,10 @@ """.strip() +) -xslt_input_html = b""" +xslt_input_html = ( + b""" @@ -45,9 +45,11 @@ """.strip() +) -xslt_output = b""" +xslt_output = ( + b""" @@ -60,8 +62,10 @@ """.strip() +) -xslt_output_html = b""" +xslt_output_html = ( + b""" @@ -77,129 +81,345 @@

""".strip() +) + + +@pytest.fixture(scope="class") +def xslt_files(base_dir): + base_dir.join("fi_in.xsl").write(xslt_input) + base_dir.join("fi_in_html.xsl").write(xslt_input_html) + base_dir.join("fi_out.xsl").write(xslt_output) + base_dir.join("fi_out_html.xsl").write(xslt_output_html) + + -def setup_module(): - module_setup(test_config, 'xslt_featureinfo.yaml') - with open(os.path.join(test_config['base_dir'], 'fi_in.xsl'), 'wb') as f: - f.write(xslt_input) - with open(os.path.join(test_config['base_dir'], 'fi_in_html.xsl'), 'wb') as f: - f.write(xslt_input_html) - with open(os.path.join(test_config['base_dir'], 'fi_out.xsl'), 'wb') as f: - f.write(xslt_output) - with open(os.path.join(test_config['base_dir'], 'fi_out_html.xsl'), 'wb') as f: - f.write(xslt_output_html) -def teardown_module(): - module_teardown(test_config) +TESTSERVER_ADDRESS = "localhost", 42423 -TESTSERVER_ADDRESS = 'localhost', 42423 -class TestWMSXSLTFeatureInfo(SystemTest): - config = test_config +@pytest.mark.usefixtures("xslt_files") +class TestWMSXSLTFeatureInfo(SysTest): + + @pytest.fixture(scope="class") + def config_file(self): + return "xslt_featureinfo.yaml" + def setup(self): - SystemTest.setup(self) - self.common_fi_req = WMS111FeatureInfoRequest(url='/service?', - param=dict(x='10', y='20', width='200', height='200', layers='fi_layer', - format='image/png', query_layers='fi_layer', styles='', - bbox='1000,400,2000,1400', srs='EPSG:900913')) + self.common_fi_req = WMS111FeatureInfoRequest( + url="/service?", + param=dict( + x="10", + y="20", + width="200", + height="200", + layers="fi_layer", + format="image/png", + query_layers="fi_layer", + styles="", + bbox="1000,400,2000,1400", + srs="EPSG:900913", + ), + ) - def test_get_featureinfo(self): + def test_get_featureinfo(self, app): fi_body = b"Bar" - expected_req = ({'path': r'/service_a?LAYERs=a_one&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913' - '&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=a_one&i=10&J=20&info_format=text/xml'}, - {'body': fi_body, 'headers': {'content-type': 'text/xml; charset=UTF-8'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'application/vnd.ogc.gml') - eq_(strip_whitespace(resp.body), b'Bar') + expected_req = ( + { + "path": r"/service_a?LAYERs=a_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913" + "&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=a_one&i=10&J=20&info_format=text/xml" + }, + {"body": fi_body, "headers": {"content-type": "text/xml; charset=UTF-8"}}, + ) + with mock_httpd(TESTSERVER_ADDRESS, [expected_req]): + resp = app.get(self.common_fi_req) + assert resp.content_type == "application/vnd.ogc.gml" + assert strip_whitespace(resp.body) == b"Bar" - def test_get_featureinfo_130(self): + def test_get_featureinfo_130(self, app): fi_body = b"Bar" - expected_req = ({'path': r'/service_a?LAYERs=a_one&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913' - '&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=a_one&i=10&J=20&info_format=text/xml'}, - {'body': fi_body, 'headers': {'content-type': 'text/xml'}}) - with mock_httpd(('localhost', 42423), [expected_req]): - req = WMS130FeatureInfoRequest(url='/service?').copy_with_request_params(self.common_fi_req) - resp = self.app.get(req) - eq_(resp.content_type, 'text/xml') - eq_(strip_whitespace(resp.body), b'Bar') + expected_req = ( + { + "path": r"/service_a?LAYERs=a_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913" + "&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=a_one&i=10&J=20&info_format=text/xml" + }, + {"body": fi_body, "headers": {"content-type": "text/xml"}}, + ) + with mock_httpd(TESTSERVER_ADDRESS, [expected_req]): + req = WMS130FeatureInfoRequest(url="/service?").copy_with_request_params( + self.common_fi_req + ) + resp = app.get(req) + assert resp.content_type == "text/xml" + assert strip_whitespace(resp.body) == b"Bar" + + def test_get_multiple_featureinfo(self, app): + fi_body1 = b"Bar1" + fi_body2 = b"Bar2" + fi_body3 = b"

Hello

Bar3" + expected_req1 = ( + { + "path": r"/service_a?LAYERs=a_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913" + "&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=a_one&i=10&J=20&info_format=text/xml" + }, + {"body": fi_body1, "headers": {"content-type": "text/xml"}}, + ) + expected_req2 = ( + { + "path": r"/service_b?LAYERs=b_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913" + "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=b_one&X=10&Y=20&info_format=text/xml" + }, + {"body": fi_body2, "headers": {"content-type": "text/xml"}}, + ) + expected_req3 = ( + { + "path": r"/service_d?LAYERs=d_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913" + "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=d_one&X=10&Y=20&info_format=text/html" + }, + {"body": fi_body3, "headers": {"content-type": "text/html"}}, + ) + with mock_httpd( + TESTSERVER_ADDRESS, [expected_req1, expected_req2, expected_req3] + ): + self.common_fi_req.params["layers"] = "fi_multi_layer" + self.common_fi_req.params["query_layers"] = "fi_multi_layer" + resp = app.get(self.common_fi_req) + assert resp.content_type == "application/vnd.ogc.gml" + assert ( + strip_whitespace(resp.body) + == b"Bar1Bar2Bar3" + ) - def test_get_multiple_featureinfo(self): + def test_get_multiple_featureinfo_html_out(self, app): fi_body1 = b"Bar1" fi_body2 = b"Bar2" fi_body3 = b"

Hello

Bar3" - expected_req1 = ({'path': r'/service_a?LAYERs=a_one&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913' - '&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=a_one&i=10&J=20&info_format=text/xml'}, - {'body': fi_body1, 'headers': {'content-type': 'text/xml'}}) - expected_req2 = ({'path': r'/service_b?LAYERs=b_one&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913' - '&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=b_one&X=10&Y=20&info_format=text/xml'}, - {'body': fi_body2, 'headers': {'content-type': 'text/xml'}}) - expected_req3 = ({'path': r'/service_d?LAYERs=d_one&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913' - '&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=d_one&X=10&Y=20&info_format=text/html'}, - {'body': fi_body3, 'headers': {'content-type': 'text/html'}}) - with mock_httpd(('localhost', 42423), [expected_req1, expected_req2, expected_req3]): - self.common_fi_req.params['layers'] = 'fi_multi_layer' - self.common_fi_req.params['query_layers'] = 'fi_multi_layer' - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'application/vnd.ogc.gml') - eq_(strip_whitespace(resp.body), - b'Bar1Bar2Bar3') + expected_req1 = ( + { + "path": r"/service_a?LAYERs=a_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913" + "&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=a_one&i=10&J=20&info_format=text/xml" + }, + {"body": fi_body1, "headers": {"content-type": "text/xml"}}, + ) + expected_req2 = ( + { + "path": r"/service_b?LAYERs=b_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913" + "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=b_one&X=10&Y=20&info_format=text/xml" + }, + {"body": fi_body2, "headers": {"content-type": "text/xml"}}, + ) + expected_req3 = ( + { + "path": r"/service_d?LAYERs=d_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913" + "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=d_one&X=10&Y=20&info_format=text/html" + }, + {"body": fi_body3, "headers": {"content-type": "text/html"}}, + ) + with mock_httpd( + TESTSERVER_ADDRESS, [expected_req1, expected_req2, expected_req3] + ): + self.common_fi_req.params["layers"] = "fi_multi_layer" + self.common_fi_req.params["query_layers"] = "fi_multi_layer" + self.common_fi_req.params["info_format"] = "text/html" + resp = app.get(self.common_fi_req) + assert resp.content_type == "text/html" + assert ( + strip_whitespace(resp.body) + == b"

Bars

Bar1

Bar2

Bar3

" + ) + + def test_mixed_featureinfo(self, app): + fi_body1 = b"Hello" + fi_body2 = b"Bar2" + expected_req1 = ( + { + "path": r"/service_c?LAYERs=c_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913" + "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=c_one&X=10&Y=20" + }, + {"body": fi_body1, "headers": {"content-type": "text/plain"}}, + ) + expected_req2 = ( + { + "path": r"/service_a?LAYERs=a_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913" + "&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=a_one&i=10&J=20&info_format=text/xml" + }, + {"body": fi_body2, "headers": {"content-type": "text/xml"}}, + ) + with mock_httpd(TESTSERVER_ADDRESS, [expected_req1, expected_req2]): + self.common_fi_req.params["layers"] = "fi_without_xslt_layer,fi_layer" + self.common_fi_req.params["query_layers"] = "fi_without_xslt_layer,fi_layer" + resp = app.get(self.common_fi_req) + assert resp.content_type == "text/plain" + assert strip_whitespace(resp.body) == b"HelloBar2" + + +@pytest.mark.usefixtures("xslt_files") +class TestWMSXSLTFeatureInfoInput(SysTest): + """ + Test XSL transformations that are only applied to the incoming feature info documents. + """ + + @pytest.fixture(scope="class") + def config_file(self): + return "xslt_featureinfo_input.yaml" + + def setup(self): + self.common_fi_req = WMS111FeatureInfoRequest( + url="/service?", + param=dict( + x="10", + y="20", + width="200", + height="200", + layers="fi_layer", + format="image/png", + query_layers="fi_layer", + styles="", + bbox="1000,400,2000,1400", + srs="EPSG:900913", + ), + ) + + def test_get_featureinfo(self, app): + fi_body = b"Bar" + expected_req = ( + { + "path": r"/service_a?LAYERs=a_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913" + "&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=a_one&i=10&J=20&info_format=text/xml" + }, + {"body": fi_body, "headers": {"content-type": "text/xml; charset=UTF-8"}}, + ) + with mock_httpd(TESTSERVER_ADDRESS, [expected_req]): + resp = app.get(self.common_fi_req) + assert resp.content_type == "application/vnd.ogc.gml" + assert strip_whitespace(resp.body) == b"Bar" + + + def test_get_featureinfo_ignore_content_type(self, app): + fi_body = b"Bar" + expected_req = ( + { + "path": r"/service_a?LAYERs=a_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913" + "&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=a_one&i=10&J=20&info_format=text/xml" + }, + {"body": fi_body, "headers": {"content-type": "text/mycustom_xml"}}, # ignored because layer has featureinfo_format + ) + with mock_httpd(TESTSERVER_ADDRESS, [expected_req]): + self.common_fi_req.params["info_format"] = "text/xml" + resp = app.get(self.common_fi_req) + assert resp.content_type == "application/vnd.ogc.gml" + assert strip_whitespace(resp.body) == b"Bar" + + def test_get_featureinfo_130(self, app): + fi_body = b"Bar" + expected_req = ( + { + "path": r"/service_a?LAYERs=a_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913" + "&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=a_one&i=10&J=20&info_format=text/xml" + }, + {"body": fi_body, "headers": {"content-type": "text/xml"}}, + ) + with mock_httpd(TESTSERVER_ADDRESS, [expected_req]): + req = WMS130FeatureInfoRequest(url="/service?").copy_with_request_params( + self.common_fi_req + ) + resp = app.get(req) + assert resp.content_type == "text/xml" + assert strip_whitespace(resp.body) == b"Bar" - def test_get_multiple_featureinfo_html_out(self): + def test_get_multiple_featureinfo(self, app): fi_body1 = b"Bar1" fi_body2 = b"Bar2" fi_body3 = b"

Hello

Bar3" - expected_req1 = ({'path': r'/service_a?LAYERs=a_one&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913' - '&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=a_one&i=10&J=20&info_format=text/xml'}, - {'body': fi_body1, 'headers': {'content-type': 'text/xml'}}) - expected_req2 = ({'path': r'/service_b?LAYERs=b_one&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913' - '&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=b_one&X=10&Y=20&info_format=text/xml'}, - {'body': fi_body2, 'headers': {'content-type': 'text/xml'}}) - expected_req3 = ({'path': r'/service_d?LAYERs=d_one&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913' - '&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=d_one&X=10&Y=20&info_format=text/html'}, - {'body': fi_body3, 'headers': {'content-type': 'text/html'}}) - with mock_httpd(('localhost', 42423), [expected_req1, expected_req2, expected_req3]): - self.common_fi_req.params['layers'] = 'fi_multi_layer' - self.common_fi_req.params['query_layers'] = 'fi_multi_layer' - self.common_fi_req.params['info_format'] = 'text/html' - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'text/html') - eq_(strip_whitespace(resp.body), - b'

Bars

Bar1

Bar2

Bar3

') + expected_req1 = ( + { + "path": r"/service_a?LAYERs=a_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913" + "&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=a_one&i=10&J=20&info_format=text/xml" + }, + {"body": fi_body1, "headers": {"content-type": "text/xml"}}, + ) + expected_req2 = ( + { + "path": r"/service_b?LAYERs=b_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913" + "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=b_one&X=10&Y=20&info_format=text/xml" + }, + {"body": fi_body2, "headers": {"content-type": "text/xml"}}, + ) + expected_req3 = ( + { + "path": r"/service_d?LAYERs=d_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913" + "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=d_one&X=10&Y=20&info_format=text/html" + }, + {"body": fi_body3, "headers": {"content-type": "text/html"}}, + ) + with mock_httpd( + TESTSERVER_ADDRESS, [expected_req1, expected_req2, expected_req3] + ): + self.common_fi_req.params["info_format"] = "text/xml" + self.common_fi_req.params["layers"] = "fi_multi_layer" + self.common_fi_req.params["query_layers"] = "fi_multi_layer" + resp = app.get(self.common_fi_req) + assert resp.content_type == "application/vnd.ogc.gml" + assert ( + strip_whitespace(resp.body) + == b"Bar1Bar2Bar3" + ) - def test_mixed_featureinfo(self): + def test_mixed_featureinfo(self, app): fi_body1 = b"Hello" fi_body2 = b"Bar2" - expected_req1 = ({'path': r'/service_c?LAYERs=c_one&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913' - '&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=c_one&X=10&Y=20'}, - {'body': fi_body1, 'headers': {'content-type': 'text/plain'}}) - expected_req2 = ({'path': r'/service_a?LAYERs=a_one&SERVICE=WMS&FORMAT=image%2Fpng' - '&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913' - '&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=' - '&WIDTH=200&QUERY_LAYERS=a_one&i=10&J=20&info_format=text/xml'}, - {'body': fi_body2, 'headers': {'content-type': 'text/xml'}}) - with mock_httpd(('localhost', 42423), [expected_req1, expected_req2]): - self.common_fi_req.params['layers'] = 'fi_without_xslt_layer,fi_layer' - self.common_fi_req.params['query_layers'] = 'fi_without_xslt_layer,fi_layer' - resp = self.app.get(self.common_fi_req) - eq_(resp.content_type, 'text/plain') - eq_(strip_whitespace(resp.body), - b'HelloBar2') \ No newline at end of file + expected_req1 = ( + { + "path": r"/service_c?LAYERs=c_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&SRS=EPSG%3A900913" + "&VERSION=1.1.1&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=c_one&X=10&Y=20" + }, + {"body": fi_body1, "headers": {"content-type": "text/plain"}}, + ) + expected_req2 = ( + { + "path": r"/service_a?LAYERs=a_one&SERVICE=WMS&FORMAT=image%2Fpng" + "&REQUEST=GetFeatureInfo&HEIGHT=200&CRS=EPSG%3A900913" + "&VERSION=1.3.0&BBOX=1000.0,400.0,2000.0,1400.0&styles=" + "&WIDTH=200&QUERY_LAYERS=a_one&i=10&J=20&info_format=text/xml" + }, + {"body": fi_body2, "headers": {"content-type": "text/xml"}}, + ) + with mock_httpd(TESTSERVER_ADDRESS, [expected_req1, expected_req2]): + self.common_fi_req.params["layers"] = "fi_without_xslt_layer,fi_layer" + self.common_fi_req.params["query_layers"] = "fi_without_xslt_layer,fi_layer" + resp = app.get(self.common_fi_req) + assert resp.content_type == "text/plain" + assert strip_whitespace(resp.body) == b"HelloBar2" diff -Nru mapproxy-1.11.0/mapproxy/test/test_http_helper.py mapproxy-1.12.0/mapproxy/test/test_http_helper.py --- mapproxy-1.11.0/mapproxy/test/test_http_helper.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/test_http_helper.py 2019-08-30 07:34:08.000000000 +0000 @@ -14,12 +14,12 @@ # limitations under the License. import requests + from mapproxy.test.http import ( MockServ, RequestsMismatchError, mock_httpd, basic_auth_value, query_eq, ) -from nose.tools import eq_ class TestMockServ(object): def test_no_requests(self): @@ -32,8 +32,8 @@ serv.expects('/test') with serv: resp = requests.get('http://localhost:%d/test' % serv.port) - eq_(resp.status_code, 200) - eq_(resp.content, b'') + assert resp.status_code == 200 + assert resp.content == b'' def test_expects_w_header(self): serv = MockServ() @@ -79,7 +79,7 @@ with serv: resp = requests.get('http://localhost:%d/test' % serv.port) assert 'Content-type' not in resp.headers - eq_(resp.content, b'hello') + assert resp.content == b'hello' def test_returns_headers(self): serv = MockServ() @@ -88,8 +88,8 @@ with serv: resp = requests.get('http://localhost:%d/test' % serv.port) - eq_(resp.headers['Content-type'], 'text/plain') - eq_(resp.content, b'hello') + assert resp.headers['Content-type'] == 'text/plain' + assert resp.content == b'hello' def test_returns_status(self): serv = MockServ() @@ -98,8 +98,8 @@ with serv: resp = requests.get('http://localhost:%d/test' % serv.port) - eq_(resp.status_code, 418) - eq_(resp.content, b'hello') + assert resp.status_code == 418 + assert resp.content == b'hello' def test_multiple_requests(self): @@ -109,9 +109,9 @@ with serv: resp = requests.get('http://localhost:%d/test1' % serv.port) - eq_(resp.content, b'hello1') + assert resp.content == b'hello1' resp = requests.get('http://localhost:%d/test2' % serv.port) - eq_(resp.content, b'hello2') + assert resp.content == b'hello2' def test_too_many_requests(self): @@ -120,7 +120,7 @@ with serv: resp = requests.get('http://localhost:%d/test1' % serv.port) - eq_(resp.content, b'hello1') + assert resp.content == b'hello1' try: requests.get('http://localhost:%d/test2' % serv.port) except requests.exceptions.RequestException: @@ -136,7 +136,7 @@ try: with serv: resp = requests.get('http://localhost:%d/test1' % serv.port) - eq_(resp.content, b'hello1') + assert resp.content == b'hello1' except RequestsMismatchError as ex: assert 'requests mismatch:\n - missing requests' in str(ex) else: @@ -149,16 +149,16 @@ with serv: resp = requests.get('http://localhost:%d/test1' % serv.port) - eq_(resp.content, b'hello1') + assert resp.content == b'hello1' resp = requests.get('http://localhost:%d/test2' % serv.port) - eq_(resp.content, b'hello2') + assert resp.content == b'hello2' serv.reset() with serv: resp = requests.get('http://localhost:%d/test2' % serv.port) - eq_(resp.content, b'hello2') + assert resp.content == b'hello2' resp = requests.get('http://localhost:%d/test1' % serv.port) - eq_(resp.content, b'hello1') + assert resp.content == b'hello1' def test_unexpected(self): serv = MockServ(unordered=True) @@ -168,7 +168,7 @@ try: with serv: resp = requests.get('http://localhost:%d/test1' % serv.port) - eq_(resp.content, b'hello1') + assert resp.content == b'hello1' try: requests.get('http://localhost:%d/test3' % serv.port) except requests.exceptions.RequestException: @@ -176,7 +176,7 @@ else: raise AssertionError('RequestException expected') resp = requests.get('http://localhost:%d/test2' % serv.port) - eq_(resp.content, b'hello2') + assert resp.content == b'hello2' except RequestsMismatchError as ex: assert 'unexpected request' in ex.assertions[0] else: @@ -200,13 +200,13 @@ ({'path':'/test', 'headers': {'Accept': 'Coffee'}, 'require_basic_auth': True}, {'body': b'ok', 'status': 418})]): resp = requests.get('http://localhost:42423/test') - eq_(resp.status_code, 401) - eq_(resp.content, b'no access') + assert resp.status_code == 401 + assert resp.content == b'no access' resp = requests.get('http://localhost:42423/test', headers={ 'Authorization': basic_auth_value('foo', 'bar'), 'Accept': 'Coffee'} ) - eq_(resp.content, b'ok') + assert resp.content == b'ok' def test_query_eq(): @@ -216,4 +216,4 @@ assert not query_eq('?baz=42.00000001&foo=bar', '?foo=bar&baz=42.0') assert query_eq('?baz=42.000000001,23.99999999999&foo=bar', '?foo=bar&baz=42.0,24.0') - assert not query_eq('?baz=42.00000001&foo=bar', '?foo=bar&baz=42.0') \ No newline at end of file + assert not query_eq('?baz=42.00000001&foo=bar', '?foo=bar&baz=42.0') diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_async.py mapproxy-1.12.0/mapproxy/test/unit/test_async.py --- mapproxy-1.11.0/mapproxy/test/unit/test_async.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_async.py 2019-08-30 07:34:08.000000000 +0000 @@ -17,10 +17,11 @@ import time import threading -from mapproxy.util.async import imap_async_threaded, ThreadPool -from nose.tools import eq_ -from nose.plugins.skip import SkipTest +import pytest + +from mapproxy.util.async_ import imap, ThreadPool + class TestThreaded(object): def test_map(self): @@ -28,76 +29,39 @@ time.sleep(0.05) return x start = time.time() - result = list(imap_async_threaded(func, list(range(40)))) + result = list(imap(func, list(range(40)))) stop = time.time() duration = stop - start assert duration < 0.5, "took %s" % duration - eq_(len(result), 40) + assert len(result) == 40 def test_map_with_exception(self): def func(x): raise Exception() try: - list(imap_async_threaded(func, list(range(40)))) + list(imap(func, list(range(40)))) except Exception: pass else: assert False, 'exception expected' -try: - import eventlet - from mapproxy.util.async import imap_async_eventlet, EventletPool - _has_eventlet = True -except ImportError: - _has_eventlet = False - -class TestEventlet(object): - def setup(self): - if not _has_eventlet: - raise SkipTest('eventlet required') - - def test_map(self): - def func(x): - eventlet.sleep(0.05) - return x - start = time.time() - result = list(imap_async_eventlet(func, list(range(40)))) - stop = time.time() - - duration = stop - start - assert duration < 0.2, "took %s" % duration - - eq_(len(result), 40) - - def test_map_with_exception(self): - def func(x): - raise Exception() - - try: - list(imap_async_eventlet(func, list(range(40)))) - except Exception: - pass - else: - assert False, 'exception expected' - - class CommonPoolTests(object): def _check_single_arg(self, func): result = list(func()) - eq_(result, [3]) + assert result == [3] def test_single_argument(self): f1 = lambda x, y: x+y pool = self.mk_pool() check = self._check_single_arg - yield check, lambda: pool.map(f1, [1], [2]) - yield check, lambda: pool.imap(f1, [1], [2]) - yield check, lambda: pool.starmap(f1, [(1, 2)]) - yield check, lambda: pool.starcall([(f1, 1, 2)]) + check(lambda: pool.map(f1, [1], [2])) + check(lambda: pool.imap(f1, [1], [2])) + check(lambda: pool.starmap(f1, [(1, 2)])) + check(lambda: pool.starcall([(f1, 1, 2)])) def _check_single_arg_raise(self, func): @@ -113,10 +77,10 @@ raise ValueError pool = self.mk_pool() check = self._check_single_arg_raise - yield check, lambda: pool.map(f1, [1], [2]) - yield check, lambda: pool.imap(f1, [1], [2]) - yield check, lambda: pool.starmap(f1, [(1, 2)]) - yield check, lambda: pool.starcall([(f1, 1, 2)]) + check(lambda: pool.map(f1, [1], [2])) + check(lambda: pool.imap(f1, [1], [2])) + check(lambda: pool.starmap(f1, [(1, 2)])) + check(lambda: pool.starcall([(f1, 1, 2)])) def _check_single_arg_result_object(self, func): result = list(func()) @@ -128,30 +92,30 @@ raise ValueError pool = self.mk_pool() check = self._check_single_arg_result_object - yield check, lambda: pool.map(f1, [1], [2], use_result_objects=True) - yield check, lambda: pool.imap(f1, [1], [2], use_result_objects=True) - yield check, lambda: pool.starmap(f1, [(1, 2)], use_result_objects=True) - yield check, lambda: pool.starcall([(f1, 1, 2)], use_result_objects=True) + check(lambda: pool.map(f1, [1], [2], use_result_objects=True)) + check(lambda: pool.imap(f1, [1], [2], use_result_objects=True)) + check(lambda: pool.starmap(f1, [(1, 2)], use_result_objects=True)) + check(lambda: pool.starcall([(f1, 1, 2)], use_result_objects=True)) def _check_multiple_args(self, func): result = list(func()) - eq_(result, [3, 5]) + assert result == [3, 5] def test_multiple_arguments(self): f1 = lambda x, y: x+y pool = self.mk_pool() check = self._check_multiple_args - yield check, lambda: pool.map(f1, [1, 2], [2, 3]) - yield check, lambda: pool.imap(f1, [1, 2], [2, 3]) - yield check, lambda: pool.starmap(f1, [(1, 2), (2, 3)]) - yield check, lambda: pool.starcall([(f1, 1, 2), (f1, 2, 3)]) + check(lambda: pool.map(f1, [1, 2], [2, 3])) + check(lambda: pool.imap(f1, [1, 2], [2, 3])) + check(lambda: pool.starmap(f1, [(1, 2), (2, 3)])) + check(lambda: pool.starcall([(f1, 1, 2), (f1, 2, 3)])) def _check_multiple_args_with_exceptions_result_object(self, func): result = list(func()) - eq_(result[0].result, 3) - eq_(type(result[1].exception[1]), ValueError) - eq_(result[2].result, 7) + assert result[0].result == 3 + assert type(result[1].exception[1]) == ValueError + assert result[2].result == 7 def test_multiple_arguments_exceptions_result_object(self): def f1(x, y): @@ -160,17 +124,17 @@ return x+y pool = self.mk_pool() check = self._check_multiple_args_with_exceptions_result_object - yield check, lambda: pool.map(f1, [1, 2, 3], [2, 3, 4], use_result_objects=True) - yield check, lambda: pool.imap(f1, [1, 2, 3], [2, 3, 4], use_result_objects=True) - yield check, lambda: pool.starmap(f1, [(1, 2), (2, 3), (3, 4)], use_result_objects=True) - yield check, lambda: pool.starcall([(f1, 1, 2), (f1, 2, 3), (f1, 3, 4)], use_result_objects=True) + check(lambda: pool.map(f1, [1, 2, 3], [2, 3, 4], use_result_objects=True)) + check(lambda: pool.imap(f1, [1, 2, 3], [2, 3, 4], use_result_objects=True)) + check(lambda: pool.starmap(f1, [(1, 2), (2, 3), (3, 4)], use_result_objects=True)) + check(lambda: pool.starcall([(f1, 1, 2), (f1, 2, 3), (f1, 3, 4)], use_result_objects=True)) def _check_multiple_args_with_exceptions(self, func): result = func() try: # first result might aleady raise the exception when # when second result is returned faster by the ThreadPoolWorker - eq_(next(result), 3) + assert next(result) == 3 next(result) except ValueError: pass @@ -192,10 +156,10 @@ pass else: assert False, 'expected ValueError' - yield check_pool_map - yield check, lambda: pool.imap(f1, [1, 2, 3], [2, 3, 4]) - yield check, lambda: pool.starmap(f1, [(1, 2), (2, 3), (3, 4)]) - yield check, lambda: pool.starcall([(f1, 1, 2), (f1, 2, 3), (f1, 3, 4)]) + check_pool_map() + check(lambda: pool.imap(f1, [1, 2, 3], [2, 3, 4])) + check(lambda: pool.starmap(f1, [(1, 2), (2, 3), (3, 4)])) + check(lambda: pool.starcall([(f1, 1, 2), (f1, 2, 3), (f1, 3, 4)])) @@ -253,65 +217,6 @@ assert not error_occured assert 'bar' in base_config() - -class TestEventletPool(CommonPoolTests): - def setup(self): - if not _has_eventlet: - raise SkipTest('eventlet required') - - def mk_pool(self): - if not _has_eventlet: - raise SkipTest('eventlet required') - return EventletPool() - - def test_base_config(self): - # test that all concurrent have access to their - # local base_config - from mapproxy.config import base_config - from mapproxy.config import local_base_config - from copy import deepcopy - - # make two separate base_configs - conf1 = deepcopy(base_config()) - conf1.conf = 1 - conf2 = deepcopy(base_config()) - conf2.conf = 2 - base_config().bar = 'baz' - - # run test in parallel, check1 and check2 should interleave - # each with their local conf - - error_occured = False - - def check1(x): - global error_occured - if base_config().conf != 1 or 'bar' in base_config(): - error_occured = True - - def check2(x): - global error_occured - if base_config().conf != 2 or 'bar' in base_config(): - error_occured = True - - assert 'bar' in base_config() - - def test1(): - with local_base_config(conf1): - pool1 = EventletPool(5) - list(pool1.imap(check1, list(range(200)))) - - def test2(): - with local_base_config(conf2): - pool2 = EventletPool(5) - list(pool2.imap(check2, list(range(200)))) - - t1 = eventlet.spawn(test1) - t2 = eventlet.spawn(test2) - t1.wait() - t2.wait() - assert not error_occured - assert 'bar' in base_config() - class DummyException(Exception): pass diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_auth.py mapproxy-1.12.0/mapproxy/test/unit/test_auth.py --- mapproxy-1.11.0/mapproxy/test/unit/test_auth.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_auth.py 2019-08-30 07:34:08.000000000 +0000 @@ -1,16 +1,33 @@ +# This file is part of the MapProxy project. +# Copyright (C) 2018 Omniscale +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from mapproxy.exception import RequestError from mapproxy.grid import tile_grid -from mapproxy.layer import MapLayer, DefaultMapExtent from mapproxy.image import BlankImageSource from mapproxy.image.opts import ImageOptions +from mapproxy.layer import MapLayer, DefaultMapExtent from mapproxy.request.base import Request -from mapproxy.exception import RequestError -from mapproxy.request.wms import wms_request from mapproxy.request.tile import tile_request -from mapproxy.service.wms import WMSLayer, WMSGroupLayer, WMSServer -from mapproxy.service.tile import TileServer +from mapproxy.request.wms import wms_request from mapproxy.service.kml import KMLServer, kml_request +from mapproxy.service.tile import TileServer +from mapproxy.service.wms import WMSLayer, WMSGroupLayer, WMSServer from mapproxy.test.http import make_wsgi_env -from nose.tools import raises, eq_ + class DummyLayer(MapLayer): transparent = True @@ -34,6 +51,7 @@ MAP_REQ = "FORMAT=image%2Fpng&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&SRS=EPSG%3A4326&BBOX=5,46,8,48&WIDTH=60&HEIGHT=40" FI_REQ = "FORMAT=image%2Fpng&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetFeatureInfo&STYLES=&SRS=EPSG%3A4326&BBOX=5,46,8,48&WIDTH=60&HEIGHT=40&X=30&Y=20" + class TestWMSAuth(object): def setup(self): layers = {} @@ -91,7 +109,7 @@ def test_allow_all(self): def auth(service, layers, **kw): - eq_(layers, 'layer1a layer1b'.split()) + assert layers == 'layer1a layer1b'.split() return { 'authorized': 'full' } self.server.map(self.map_request('layer1', auth)) assert self.layers['layer1a'].requested @@ -100,7 +118,7 @@ def test_root_with_partial_sublayers(self): # filter out sublayer layer1b def auth(service, layers, **kw): - eq_(layers, 'layer1a layer1b'.split()) + assert layers == 'layer1a layer1b'.split() return { 'authorized': 'partial', 'layers': { @@ -115,7 +133,7 @@ def test_accept_sublayer(self): def auth(service, layers, **kw): - eq_(layers, 'layer1a'.split()) + assert layers == 'layer1a'.split() return { 'authorized': 'partial', 'layers': { @@ -130,7 +148,7 @@ def test_accept_sublayer_w_root_denied(self): def auth(service, layers, **kw): - eq_(layers, 'layer1a'.split()) + assert layers == 'layer1a'.split() return { 'authorized': 'partial', 'layers': { @@ -143,10 +161,9 @@ assert self.layers['layer1a'].requested assert not self.layers['layer1b'].requested - @raises(RequestError) def test_deny_sublayer(self): def auth(service, layers, **kw): - eq_(layers, 'layer1b'.split()) + assert layers == 'layer1b'.split() return { 'authorized': 'partial', 'layers': { @@ -155,23 +172,25 @@ 'layer1b': {'map': False}, } } - self.server.map(self.map_request('layer1b', auth)) - @raises(RequestError) + with pytest.raises(RequestError): + self.server.map(self.map_request('layer1b', auth)) + def test_deny_group_layer_w_source(self): def auth(service, layers, **kw): - eq_(layers, 'layer2b'.split()) + assert layers == 'layer2b'.split() return { 'authorized': 'partial', 'layers': { 'layer2b': {'map': False}, } } - self.server.map(self.map_request('layer2b', auth)) + with pytest.raises(RequestError): + self.server.map(self.map_request('layer2b', auth)) def test_nested_layers_with_partial_sublayers(self): def auth(service, layers, **kw): - eq_(layers, 'layer1a layer1b layer2a layer2b'.split()) + assert layers == 'layer1a layer1b layer2a layer2b'.split() return { 'authorized': 'partial', 'layers': { @@ -190,7 +209,7 @@ def test_unauthenticated(self): def auth(service, layers, **kw): - eq_(layers, 'layer1b'.split()) + assert layers == 'layer1b'.split() return { 'authorized': 'unauthenticated', } @@ -211,7 +230,7 @@ def test_root_with_partial_sublayers(self): # filter out sublayer layer1b def auth(service, layers, **kw): - eq_(layers, 'layer1a layer1b'.split()) + assert layers == 'layer1a layer1b'.split() return { 'authorized': 'partial', 'layers': { @@ -226,7 +245,7 @@ def test_accept_sublayer(self): def auth(service, layers, **kw): - eq_(layers, 'layer1a'.split()) + assert layers == 'layer1a'.split() return { 'authorized': 'partial', 'layers': { @@ -241,7 +260,7 @@ def test_accept_sublayer_w_root_denied(self): def auth(service, layers, **kw): - eq_(layers, 'layer1a'.split()) + assert layers == 'layer1a'.split() return { 'authorized': 'partial', 'layers': { @@ -254,10 +273,9 @@ assert self.layers['layer1a'].queried assert not self.layers['layer1b'].queried - @raises(RequestError) def test_deny_sublayer(self): def auth(service, layers, **kw): - eq_(layers, 'layer1b'.split()) + assert layers == 'layer1b'.split() return { 'authorized': 'partial', 'layers': { @@ -266,23 +284,24 @@ 'layer1b': {'featureinfo': False}, } } - self.server.featureinfo(self.fi_request('layer1b', auth)) + with pytest.raises(RequestError): + self.server.featureinfo(self.fi_request('layer1b', auth)) - @raises(RequestError) def test_deny_group_layer_w_source(self): def auth(service, layers, **kw): - eq_(layers, 'layer2b'.split()) + assert layers == 'layer2b'.split() return { 'authorized': 'partial', 'layers': { 'layer2b': {'featureinfo': False}, } } - self.server.featureinfo(self.fi_request('layer2b', auth)) + with pytest.raises(RequestError): + self.server.featureinfo(self.fi_request('layer2b', auth)) def test_nested_layers_with_partial_sublayers(self): def auth(service, layers, **kw): - eq_(layers, 'layer1a layer1b layer2a layer2b'.split()) + assert layers == 'layer1a layer1b layer2a layer2b'.split() return { 'authorized': 'partial', 'layers': { @@ -332,21 +351,20 @@ req = Request(env) return tile_request(req) - @raises(RequestError) def test_deny_all(self): def auth(service, layers, **kw): - eq_(service, self.service) - eq_(layers, 'layer1'.split()) + assert service == self.service + assert layers == 'layer1'.split() return { 'authorized': 'none', } - self.server.map(self.tile_request('layer1/0/0/0.png', auth)) + with pytest.raises(RequestError): + self.server.map(self.tile_request('layer1/0/0/0.png', auth)) - @raises(RequestError) def test_deny_layer(self): def auth(service, layers, **kw): - eq_(service, self.service) - eq_(layers, 'layer1'.split()) + assert service == self.service + assert layers == 'layer1'.split() return { 'authorized': 'partial', 'layers': { @@ -354,12 +372,13 @@ 'layer2': {'tile': True}, } } - self.server.map(self.tile_request('layer1/0/0/0.png', auth)) + with pytest.raises(RequestError): + self.server.map(self.tile_request('layer1/0/0/0.png', auth)) def test_allow_all(self): def auth(service, layers, **kw): - eq_(service, self.service) - eq_(layers, 'layer1'.split()) + assert service == self.service + assert layers == 'layer1'.split() return { 'authorized': 'full', } @@ -368,8 +387,8 @@ def test_allow_layer(self): def auth(service, layers, **kw): - eq_(service, self.service) - eq_(layers, 'layer1'.split()) + assert service == self.service + assert layers == 'layer1'.split() return { 'authorized': 'partial', 'layers': { diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_cache_compact.py mapproxy-1.12.0/mapproxy/test/unit/test_cache_compact.py --- mapproxy-1.11.0/mapproxy/test/unit/test_cache_compact.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_cache_compact.py 2019-08-30 07:34:08.000000000 +0000 @@ -30,7 +30,6 @@ from mapproxy.script.defrag import defrag_compact_cache from mapproxy.test.unit.test_cache_tile import TileCacheTestBase -from nose.tools import eq_ class TestCompactCacheV1(TileCacheTestBase): @@ -106,13 +105,13 @@ def assert_header(tile_bytes_written, max_tile_bytes): with open(os.path.join(self.cache_dir, 'L12', 'R0380C1380.bundle'), 'r+b') as f: header = struct.unpack(' (3, 7), reason="riak is not compatible with this Python version") class RiakCacheTestBase(TileCacheTestBase): always_loads_metadata = True def setup(self): - if not os.environ.get(self.riak_url_env): - raise SkipTest() - url = os.environ[self.riak_url_env] urlparts = urlparse.urlparse(url) protocol = urlparts.scheme.lower() @@ -63,8 +63,14 @@ assert self.cache.remove_tile(tile) assert self.cache.remove_tile(tile) + +@pytest.mark.skipif(not os.environ.get('MAPPROXY_TEST_RIAK_HTTP'), + reason="MAPPROXY_TEST_RIAK_HTTP not set") class TestRiakCacheHTTP(RiakCacheTestBase): riak_url_env = 'MAPPROXY_TEST_RIAK_HTTP' + +@pytest.mark.skipif(not os.environ.get('MAPPROXY_TEST_RIAK_PBC'), + reason="MAPPROXY_TEST_RIAK_PBC not set") class TestRiakCachePBC(RiakCacheTestBase): - riak_url_env = 'MAPPROXY_TEST_RIAK_PBC' \ No newline at end of file + riak_url_env = 'MAPPROXY_TEST_RIAK_PBC' diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_cache_s3.py mapproxy-1.12.0/mapproxy/test/unit/test_cache_s3.py --- mapproxy-1.11.0/mapproxy/test/unit/test_cache_s3.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_cache_s3.py 2019-08-30 07:34:08.000000000 +0000 @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pytest + try: import boto3 from moto import mock_s3 @@ -20,20 +22,17 @@ boto3 = None mock_s3 = None -from nose.plugins.skip import SkipTest - from mapproxy.cache.s3 import S3Cache from mapproxy.test.unit.test_cache_tile import TileCacheTestBase +@pytest.mark.skipif(not mock_s3 or not boto3, + reason="boto3 and moto required for S3 tests") class TestS3Cache(TileCacheTestBase): always_loads_metadata = True uses_utc = True def setup(self): - if not mock_s3 or not boto3: - raise SkipTest("boto3 and moto required for S3 tests") - TileCacheTestBase.setup(self) self.mock = mock_s3() @@ -56,30 +55,30 @@ self.mock.stop() TileCacheTestBase.teardown(self) - def check_tile_key(self, layout, tile_coord, key): + @pytest.mark.parametrize('layout,tile_coord,key', [ + ['mp', (12345, 67890, 2), 'mycache/webmercator/02/0001/2345/0006/7890.png'], + ['mp', (12345, 67890, 12), 'mycache/webmercator/12/0001/2345/0006/7890.png'], + + ['tc', (12345, 67890, 2), 'mycache/webmercator/02/000/012/345/000/067/890.png'], + ['tc', (12345, 67890, 12), 'mycache/webmercator/12/000/012/345/000/067/890.png'], + + ['tms', (12345, 67890, 2), 'mycache/webmercator/2/12345/67890.png'], + ['tms', (12345, 67890, 12), 'mycache/webmercator/12/12345/67890.png'], + + ['quadkey', (0, 0, 0), 'mycache/webmercator/.png'], + ['quadkey', (0, 0, 1), 'mycache/webmercator/0.png'], + ['quadkey', (1, 1, 1), 'mycache/webmercator/3.png'], + ['quadkey', (12345, 67890, 12), 'mycache/webmercator/200200331021.png'], + + ['arcgis', (1, 2, 3), 'mycache/webmercator/L03/R00000002/C00000001.png'], + ['arcgis', (9, 2, 3), 'mycache/webmercator/L03/R00000002/C00000009.png'], + ['arcgis', (10, 2, 3), 'mycache/webmercator/L03/R00000002/C0000000a.png'], + ['arcgis', (12345, 67890, 12), 'mycache/webmercator/L12/R00010932/C00003039.png'], + ]) + def test_tile_key(self, layout, tile_coord, key): cache = S3Cache('/mycache/webmercator', 'png', bucket_name=self.bucket_name, directory_layout=layout) cache.store_tile(self.create_tile(tile_coord)) # raises, if key is missing boto3.client("s3").head_object(Bucket=self.bucket_name, Key=key) - def test_tile_keys(self): - yield self.check_tile_key, 'mp', (12345, 67890, 2), 'mycache/webmercator/02/0001/2345/0006/7890.png' - yield self.check_tile_key, 'mp', (12345, 67890, 12), 'mycache/webmercator/12/0001/2345/0006/7890.png' - - yield self.check_tile_key, 'tc', (12345, 67890, 2), 'mycache/webmercator/02/000/012/345/000/067/890.png' - yield self.check_tile_key, 'tc', (12345, 67890, 12), 'mycache/webmercator/12/000/012/345/000/067/890.png' - - yield self.check_tile_key, 'tms', (12345, 67890, 2), 'mycache/webmercator/2/12345/67890.png' - yield self.check_tile_key, 'tms', (12345, 67890, 12), 'mycache/webmercator/12/12345/67890.png' - - yield self.check_tile_key, 'quadkey', (0, 0, 0), 'mycache/webmercator/.png' - yield self.check_tile_key, 'quadkey', (0, 0, 1), 'mycache/webmercator/0.png' - yield self.check_tile_key, 'quadkey', (1, 1, 1), 'mycache/webmercator/3.png' - yield self.check_tile_key, 'quadkey', (12345, 67890, 12), 'mycache/webmercator/200200331021.png' - - yield self.check_tile_key, 'arcgis', (1, 2, 3), 'mycache/webmercator/L03/R00000002/C00000001.png' - yield self.check_tile_key, 'arcgis', (9, 2, 3), 'mycache/webmercator/L03/R00000002/C00000009.png' - yield self.check_tile_key, 'arcgis', (10, 2, 3), 'mycache/webmercator/L03/R00000002/C0000000a.png' - yield self.check_tile_key, 'arcgis', (12345, 67890, 12), 'mycache/webmercator/L12/R00010932/C00003039.png' - diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_cache_tile.py mapproxy-1.12.0/mapproxy/test/unit/test_cache_tile.py --- mapproxy-1.11.0/mapproxy/test/unit/test_cache_tile.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_cache_tile.py 2019-08-30 07:34:08.000000000 +0000 @@ -13,28 +13,30 @@ # See the License for the specific language governing permissions and # limitations under the License. - import calendar import datetime import os import shutil -import threading +import sqlite3 +import sys import tempfile +import threading import time -import sqlite3 from io import BytesIO +import pytest + from PIL import Image -from mapproxy.cache.tile import Tile from mapproxy.cache.file import FileCache from mapproxy.cache.mbtiles import MBTilesCache, MBTilesLevelCache +from mapproxy.cache.tile import Tile from mapproxy.image import ImageSource from mapproxy.image.opts import ImageOptions +from mapproxy.test.helper import assert_files_in_dir from mapproxy.test.image import create_tmp_image_buf, is_png -from nose.tools import eq_ tile_image = create_tmp_image_buf((256, 256), color='blue') tile_image2 = create_tmp_image_buf((256, 256), color='red') @@ -44,10 +46,14 @@ always_loads_metadata = False uses_utc = False + cache = None # set by subclasses + def setup(self): self.cache_dir = tempfile.mkdtemp() def teardown(self): + if hasattr(self.cache, 'cleanup'): + self.cache.cleanup() if hasattr(self, 'cache_dir') and os.path.exists(self.cache_dir): shutil.rmtree(self.cache_dir) @@ -211,6 +217,8 @@ '04', '000', '000', '005', '000', '000', '012.png' ) assert os.path.exists(tile_location), tile_location + @pytest.mark.skipif(sys.platform == 'win32', + reason='link_single_color_tiles not supported on windows') def test_single_color_tile_store(self): img = Image.new('RGB', (256, 256), color='#ff0105') tile = Tile((0, 0, 4), ImageSource(img, image_opts=ImageOptions(format='image/png'))) @@ -233,6 +241,8 @@ assert loc != loc2 assert os.path.samefile(loc, loc2) + @pytest.mark.skipif(sys.platform == 'win32', + reason='link_single_color_tiles not supported on windows') def test_single_color_tile_store_w_alpha(self): img = Image.new('RGBA', (256, 256), color='#ff0105') tile = Tile((0, 0, 4), ImageSource(img, image_opts=ImageOptions(format='image/png'))) @@ -255,58 +265,53 @@ with open(loc, 'wb') as f: f.write(b'foo') - - def check_tile_location(self, layout, tile_coord, path): + @pytest.mark.parametrize('layout,tile_coord,path', [ + ['mp', (12345, 67890, 2), '/tmp/foo/02/0001/2345/0006/7890.png'], + ['mp', (12345, 67890, 12), '/tmp/foo/12/0001/2345/0006/7890.png'], + + ['tc', (12345, 67890, 2), '/tmp/foo/02/000/012/345/000/067/890.png'], + ['tc', (12345, 67890, 12), '/tmp/foo/12/000/012/345/000/067/890.png'], + + ['tms', (12345, 67890, 2), '/tmp/foo/2/12345/67890.png'], + ['tms', (12345, 67890, 12), '/tmp/foo/12/12345/67890.png'], + + ['quadkey', (0, 0, 0), '/tmp/foo/.png'], + ['quadkey', (0, 0, 1), '/tmp/foo/0.png'], + ['quadkey', (1, 1, 1), '/tmp/foo/3.png'], + ['quadkey', (12345, 67890, 12), '/tmp/foo/200200331021.png'], + + ['arcgis', (1, 2, 3), '/tmp/foo/L03/R00000002/C00000001.png'], + ['arcgis', (9, 2, 3), '/tmp/foo/L03/R00000002/C00000009.png'], + ['arcgis', (10, 2, 3), '/tmp/foo/L03/R00000002/C0000000a.png'], + ['arcgis', (12345, 67890, 12), '/tmp/foo/L12/R00010932/C00003039.png'], + ]) + def test_tile_location(self, layout, tile_coord, path): cache = FileCache('/tmp/foo', 'png', directory_layout=layout) - eq_(cache.tile_location(Tile(tile_coord)), path) - - def test_tile_locations(self): - yield self.check_tile_location, 'mp', (12345, 67890, 2), '/tmp/foo/02/0001/2345/0006/7890.png' - yield self.check_tile_location, 'mp', (12345, 67890, 12), '/tmp/foo/12/0001/2345/0006/7890.png' - - yield self.check_tile_location, 'tc', (12345, 67890, 2), '/tmp/foo/02/000/012/345/000/067/890.png' - yield self.check_tile_location, 'tc', (12345, 67890, 12), '/tmp/foo/12/000/012/345/000/067/890.png' - - yield self.check_tile_location, 'tms', (12345, 67890, 2), '/tmp/foo/2/12345/67890.png' - yield self.check_tile_location, 'tms', (12345, 67890, 12), '/tmp/foo/12/12345/67890.png' - - yield self.check_tile_location, 'quadkey', (0, 0, 0), '/tmp/foo/.png' - yield self.check_tile_location, 'quadkey', (0, 0, 1), '/tmp/foo/0.png' - yield self.check_tile_location, 'quadkey', (1, 1, 1), '/tmp/foo/3.png' - yield self.check_tile_location, 'quadkey', (12345, 67890, 12), '/tmp/foo/200200331021.png' + assert os.path.abspath(cache.tile_location(Tile(tile_coord))) == os.path.abspath(path) - yield self.check_tile_location, 'arcgis', (1, 2, 3), '/tmp/foo/L03/R00000002/C00000001.png' - yield self.check_tile_location, 'arcgis', (9, 2, 3), '/tmp/foo/L03/R00000002/C00000009.png' - yield self.check_tile_location, 'arcgis', (10, 2, 3), '/tmp/foo/L03/R00000002/C0000000a.png' - yield self.check_tile_location, 'arcgis', (12345, 67890, 12), '/tmp/foo/L12/R00010932/C00003039.png' - - - def check_level_location(self, layout, level, path): + @pytest.mark.parametrize('layout,level,path', [ + ['mp', 2, '/tmp/foo/02'], + ['mp', 12, '/tmp/foo/12'], + + ['tc', 2, '/tmp/foo/02'], + ['tc', 12, '/tmp/foo/12'], + + ['tms', '2', '/tmp/foo/2'], + ['tms', 12, '/tmp/foo/12'], + + ['arcgis', 3, '/tmp/foo/L03'], + ['arcgis', 3, '/tmp/foo/L03'], + ['arcgis', 3, '/tmp/foo/L03'], + ['arcgis', 12, '/tmp/foo/L12'], + ]) + def test_level_location(self, layout, level, path): cache = FileCache('/tmp/foo', 'png', directory_layout=layout) - eq_(cache.level_location(level), path) - - def test_level_locations(self): - yield self.check_level_location, 'mp', 2, '/tmp/foo/02' - yield self.check_level_location, 'mp', 12, '/tmp/foo/12' - - yield self.check_level_location, 'tc', 2, '/tmp/foo/02' - yield self.check_level_location, 'tc', 12, '/tmp/foo/12' - - yield self.check_level_location, 'tms', '2', '/tmp/foo/2' - yield self.check_level_location, 'tms', 12, '/tmp/foo/12' - - yield self.check_level_location, 'arcgis', 3, '/tmp/foo/L03' - yield self.check_level_location, 'arcgis', 3, '/tmp/foo/L03' - yield self.check_level_location, 'arcgis', 3, '/tmp/foo/L03' - yield self.check_level_location, 'arcgis', 12, '/tmp/foo/L12' + assert os.path.abspath(cache.level_location(level)) == os.path.abspath(path) def test_level_location_quadkey(self): - try: - self.check_level_location('quadkey', 0, None) - except NotImplementedError: - pass - else: - assert False, "expected NotImplementedError" + cache = FileCache('/tmp/foo', 'png', directory_layout='quadkey') + with pytest.raises(NotImplementedError): + cache.level_location(0) class TestMBTileCache(TileCacheTestBase): def setup(self): @@ -375,27 +380,27 @@ self.cache = MBTilesLevelCache(self.cache_dir) def test_level_files(self): - eq_(os.listdir(self.cache_dir), []) + assert_files_in_dir(self.cache_dir, []) self.cache.store_tile(self.create_tile((0, 0, 1))) - eq_(os.listdir(self.cache_dir), ['1.mbtile']) + assert_files_in_dir(self.cache_dir, ['1.mbtile'], glob='*.mbtile') self.cache.store_tile(self.create_tile((0, 0, 5))) - eq_(sorted(os.listdir(self.cache_dir)), ['1.mbtile', '5.mbtile']) + assert_files_in_dir(self.cache_dir, ['1.mbtile', '5.mbtile'], glob='*.mbtile') def test_remove_level_files(self): self.cache.store_tile(self.create_tile((0, 0, 1))) self.cache.store_tile(self.create_tile((0, 0, 2))) - eq_(sorted(os.listdir(self.cache_dir)), ['1.mbtile', '2.mbtile']) + assert_files_in_dir(self.cache_dir, ['1.mbtile', '2.mbtile'], glob='*.mbtile') self.cache.remove_level_tiles_before(1, timestamp=0) - eq_(os.listdir(self.cache_dir), ['2.mbtile']) + assert_files_in_dir(self.cache_dir, ['2.mbtile'], glob='*.mbtile') def test_remove_level_tiles_before(self): self.cache.store_tile(self.create_tile((0, 0, 1))) self.cache.store_tile(self.create_tile((0, 0, 2))) - eq_(sorted(os.listdir(self.cache_dir)), ['1.mbtile', '2.mbtile']) + assert_files_in_dir(self.cache_dir, ['1.mbtile', '2.mbtile'], glob='*.mbtile') assert self.cache.is_cached(Tile((0, 0, 1))) self.cache.remove_level_tiles_before(1, timestamp=time.time() - 60) @@ -404,7 +409,7 @@ self.cache.remove_level_tiles_before(1, timestamp=time.time() + 60) assert not self.cache.is_cached(Tile((0, 0, 1))) - eq_(sorted(os.listdir(self.cache_dir)), ['1.mbtile', '2.mbtile']) + assert_files_in_dir(self.cache_dir, ['1.mbtile', '2.mbtile'], glob='*.mbtile') assert self.cache.is_cached(Tile((0, 0, 2))) def test_bulk_store_tiles_with_different_levels(self): @@ -415,7 +420,7 @@ self.create_tile((1, 0, 1)), ]) - eq_(sorted(os.listdir(self.cache_dir)), ['1.mbtile', '2.mbtile']) + assert_files_in_dir(self.cache_dir, ['1.mbtile', '2.mbtile'], glob='*.mbtile') assert self.cache.is_cached(Tile((0, 0, 1))) assert self.cache.is_cached(Tile((1, 0, 1))) assert self.cache.is_cached(Tile((0, 0, 2))) diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_client_arcgis.py mapproxy-1.12.0/mapproxy/test/unit/test_client_arcgis.py --- mapproxy-1.11.0/mapproxy/test/unit/test_client_arcgis.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_client_arcgis.py 2019-08-30 07:34:08.000000000 +0000 @@ -18,14 +18,14 @@ from mapproxy.client.arcgis import ArcGISInfoClient from mapproxy.layer import InfoQuery from mapproxy.request.arcgis import ArcGISIdentifyRequest -from mapproxy.srs import SRS +from mapproxy.srs import SRS, SupportedSRS from mapproxy.test.http import assert_query_eq + TESTSERVER_ADDRESS = ('127.0.0.1', 56413) TESTSERVER_URL = 'http://%s:%s' % TESTSERVER_ADDRESS - class MockHTTPClient(object): def __init__(self): self.requested = [] @@ -41,7 +41,7 @@ def test_fi_request(self): req = ArcGISIdentifyRequest(url=TESTSERVER_URL + '/MapServer/export?map=foo', param={'layers':'foo'}) http = MockHTTPClient() - wms = ArcGISInfoClient(req, http_client=http, supported_srs=[SRS(4326)]) + wms = ArcGISInfoClient(req, http_client=http, supported_srs=SupportedSRS([SRS(4326)])) fi_req = InfoQuery((8, 50, 9, 51), (512, 512), SRS(4326), (128, 64), 'text/plain') @@ -58,7 +58,7 @@ def test_transform_fi_request_supported_srs(self): req = ArcGISIdentifyRequest(url=TESTSERVER_URL + '/MapServer/export?map=foo', param={'layers':'foo'}) http = MockHTTPClient() - wms = ArcGISInfoClient(req, http_client=http, supported_srs=[SRS(25832)]) + wms = ArcGISInfoClient(req, http_client=http, supported_srs=SupportedSRS([SRS(25832)])) fi_req = InfoQuery((8, 50, 9, 51), (512, 512), SRS(4326), (128, 64), 'text/plain') @@ -70,4 +70,4 @@ '&layers=foo&tolerance=5&returnGeometry=false' '&geometryType=esriGeometryPoint&geometry=447229.979084,5636149.370634' '&mapExtent=428333.552496,5538630.70275,500000.0,5650300.78652', - fuzzy_number_compare=True) \ No newline at end of file + fuzzy_number_compare=True) diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_client_cgi.py mapproxy-1.12.0/mapproxy/test/unit/test_client_cgi.py --- mapproxy-1.11.0/mapproxy/test/unit/test_client_cgi.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_client_cgi.py 2019-08-30 07:34:08.000000000 +0000 @@ -13,34 +13,36 @@ # See the License for the specific language governing permissions and # limitations under the License. +import sys import os import shutil import stat import tempfile -from mapproxy.client.http import HTTPClientError +import pytest + from mapproxy.client.cgi import CGIClient, split_cgi_response +from mapproxy.client.http import HTTPClientError from mapproxy.source import SourceError -from nose.tools import eq_ class TestSplitHTTPResponse(object): def test_n(self): - eq_(split_cgi_response(b'header1: foo\nheader2: bar\n\ncontent\n\ncontent'), - ({'Header1': 'foo', 'Header2': 'bar'}, b'content\n\ncontent')) + assert split_cgi_response(b'header1: foo\nheader2: bar\n\ncontent\n\ncontent') == \ + ({'Header1': 'foo', 'Header2': 'bar'}, b'content\n\ncontent') def test_rn(self): - eq_(split_cgi_response(b'header1\r\nheader2\r\n\r\ncontent\r\n\r\ncontent'), - ({'Header1': None, 'Header2': None}, b'content\r\n\r\ncontent')) + assert split_cgi_response(b'header1\r\nheader2\r\n\r\ncontent\r\n\r\ncontent') == \ + ({'Header1': None, 'Header2': None}, b'content\r\n\r\ncontent') def test_mixed(self): - eq_(split_cgi_response(b'header1: bar:foo\r\nheader2\n\r\ncontent\r\n\r\ncontent'), - ({'Header1': 'bar:foo', 'Header2': None}, b'content\r\n\r\ncontent')) - eq_(split_cgi_response(b'header1\r\nheader2\n\ncontent\r\n\r\ncontent'), - ({'Header1': None, 'Header2': None}, b'content\r\n\r\ncontent')) - eq_(split_cgi_response(b'header1\nheader2\r\n\r\ncontent\r\n\r\ncontent'), - ({'Header1': None, 'Header2': None}, b'content\r\n\r\ncontent')) + assert split_cgi_response(b'header1: bar:foo\r\nheader2\n\r\ncontent\r\n\r\ncontent') == \ + ({'Header1': 'bar:foo', 'Header2': None}, b'content\r\n\r\ncontent') + assert split_cgi_response(b'header1\r\nheader2\n\ncontent\r\n\r\ncontent') == \ + ({'Header1': None, 'Header2': None}, b'content\r\n\r\ncontent') + assert split_cgi_response(b'header1\nheader2\r\n\r\ncontent\r\n\r\ncontent') == \ + ({'Header1': None, 'Header2': None}, b'content\r\n\r\ncontent') def test_no_header(self): - eq_(split_cgi_response(b'content\r\ncontent'), - ({}, b'content\r\ncontent')) + assert split_cgi_response(b'content\r\ncontent') == \ + ({}, b'content\r\ncontent') TEST_CGI_SCRIPT = br"""#! /usr/bin/env python @@ -59,6 +61,8 @@ exit(2) """ +@pytest.mark.skipif(sys.platform == 'win32', + reason="tests not ported to windows") class TestCGIClient(object): def setup(self): self.script_dir = tempfile.mkdtemp() @@ -97,8 +101,8 @@ script = self.create_script() client = CGIClient(script) resp = client.open('http://example.org/service?hello=bar') - eq_(resp.headers['Content-type'], 'text/plain') - eq_(resp.read(), b'hello=bar') + assert resp.headers['Content-type'] == 'text/plain' + assert resp.read() == b'hello=bar' def test_failed_call(self): script = self.create_script(TEST_CGI_SCRIPT_FAIL) diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_client.py mapproxy-1.12.0/mapproxy/test/unit/test_client.py --- mapproxy-1.11.0/mapproxy/test/unit/test_client.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_client.py 2019-08-30 07:34:08.000000000 +0000 @@ -16,27 +16,30 @@ import os import time -import sys + +import pytest from mapproxy.client.http import HTTPClient, HTTPClientError, supports_ssl_default_context -from mapproxy.client.tile import TMSClient, TileClient, TileURLTemplate +from mapproxy.client.tile import TileClient, TileURLTemplate from mapproxy.client.wms import WMSClient, WMSInfoClient from mapproxy.grid import tile_grid from mapproxy.layer import MapQuery, InfoQuery -from mapproxy.request.wms import WMS111MapRequest, WMS100MapRequest,\ - WMS130MapRequest, WMS111FeatureInfoRequest -from mapproxy.srs import SRS -from mapproxy.test.unit.test_cache import MockHTTPClient -from mapproxy.test.http import mock_httpd, query_eq, assert_query_eq, wms_query_eq +from mapproxy.request.wms import ( + WMS111MapRequest, + WMS100MapRequest, + WMS130MapRequest, + WMS111FeatureInfoRequest, +) +from mapproxy.srs import SRS, SupportedSRS from mapproxy.test.helper import assert_re, TempFile +from mapproxy.test.http import mock_httpd, query_eq, assert_query_eq, wms_query_eq +from mapproxy.test.unit.test_cache import MockHTTPClient -from nose.tools import eq_ -from nose.plugins.skip import SkipTest -from nose.plugins.attrib import attr TESTSERVER_ADDRESS = ('127.0.0.1', 56413) TESTSERVER_URL = 'http://%s:%s' % TESTSERVER_ADDRESS + class TestHTTPClient(object): def setup(self): self.client = HTTPClient() @@ -55,6 +58,7 @@ assert_re(e.args[0], r'HTTP Error ".*": 500') else: assert False, 'expected HTTPClientError' + def test_invalid_url_type(self): try: self.client.open('htp://example.org') @@ -62,6 +66,7 @@ assert_re(e.args[0], r'No response .* "htp://example.*": unknown url type') else: assert False, 'expected HTTPClientError' + def test_invalid_url(self): try: self.client.open('this is not a url') @@ -69,6 +74,7 @@ assert_re(e.args[0], r'URL not correct "this is not.*": unknown url type') else: assert False, 'expected HTTPClientError' + def test_unknown_host(self): try: self.client.open('http://thishostshouldnotexist000136really42.org') @@ -76,6 +82,7 @@ assert_re(e.args[0], r'No response .* "http://thishost.*": .*') else: assert False, 'expected HTTPClientError' + def test_no_connect(self): try: self.client.open('http://localhost:53871') @@ -84,24 +91,23 @@ else: assert False, 'expected HTTPClientError' - @attr('online') + @pytest.mark.online def test_https_untrusted_root(self): if not supports_ssl_default_context: - # old python versions require ssl_ca_certs - raise SkipTest() + raise pytest.skip("old python versions require ssl_ca_certs") self.client = HTTPClient('https://untrusted-root.badssl.com/') try: self.client.open('https://untrusted-root.badssl.com/') except HTTPClientError as e: assert_re(e.args[0], r'Could not verify connection to URL') - @attr('online') + @pytest.mark.online def test_https_insecure(self): self.client = HTTPClient( 'https://untrusted-root.badssl.com/', insecure=True) self.client.open('https://untrusted-root.badssl.com/') - @attr('online') + @pytest.mark.online def test_https_valid_ca_cert_file(self): # verify with fixed ca_certs file cert_file = '/etc/ssl/certs/ca-certificates.crt' @@ -115,15 +121,15 @@ self.client = HTTPClient('https://www.google.com/', ssl_ca_certs=tmp) self.client.open('https://www.google.com/') - @attr('online') + @pytest.mark.online def test_https_valid_default_cert(self): # modern python should verify by default if not supports_ssl_default_context: - raise SkipTest() + raise pytest.skip("old python versions require ssl_ca_certs") self.client = HTTPClient('https://www.google.com/') self.client.open('https://www.google.com/') - @attr('online') + @pytest.mark.online def test_https_invalid_cert(self): # load 'wrong' root cert with TempFile() as tmp: @@ -235,16 +241,6 @@ -----END CERTIFICATE----- """ - -class TestTMSClient(object): - def setup(self): - self.client = TMSClient(TESTSERVER_URL) - def test_get_tile(self): - with mock_httpd(TESTSERVER_ADDRESS, [({'path': '/9/5/13.png'}, - {'body': b'tile', 'headers': {'content-type': 'image/png'}})]): - resp = self.client.get_tile((5, 13, 9)).source.read() - eq_(resp, b'tile') - class TestTileClient(object): def test_tc_path(self): template = TileURLTemplate(TESTSERVER_URL + '/%(tc_path)s.png') @@ -253,7 +249,7 @@ {'body': b'tile', 'headers': {'content-type': 'image/png'}})]): resp = client.get_tile((5, 13, 9)).source.read() - eq_(resp, b'tile') + assert resp == b'tile' def test_quadkey(self): template = TileURLTemplate(TESTSERVER_URL + '/key=%(quadkey)s&format=%(format)s') @@ -262,7 +258,7 @@ {'body': b'tile', 'headers': {'content-type': 'image/png'}})]): resp = client.get_tile((5, 13, 9)).source.read() - eq_(resp, b'tile') + assert resp == b'tile' def test_xyz(self): template = TileURLTemplate(TESTSERVER_URL + '/x=%(x)s&y=%(y)s&z=%(z)s&format=%(format)s') client = TileClient(template) @@ -270,7 +266,7 @@ {'body': b'tile', 'headers': {'content-type': 'image/png'}})]): resp = client.get_tile((5, 13, 9)).source.read() - eq_(resp, b'tile') + assert resp == b'tile' def test_arcgiscache_path(self): template = TileURLTemplate(TESTSERVER_URL + '/%(arcgiscache_path)s.png') @@ -279,7 +275,7 @@ {'body': b'tile', 'headers': {'content-type': 'image/png'}})]): resp = client.get_tile((5, 13, 9)).source.read() - eq_(resp, b'tile') + assert resp == b'tile' def test_bbox(self): grid = tile_grid(4326) @@ -289,7 +285,7 @@ {'body': b'tile', 'headers': {'content-type': 'image/png'}})]): resp = client.get_tile((0, 1, 2)).source.read() - eq_(resp, b'tile') + assert resp == b'tile' class TestCombinedWMSClient(object): def setup(self): @@ -305,8 +301,8 @@ req = MapQuery((-200000, -200000, 200000, 200000), (512, 512), SRS(900913), 'png') combined = wms1.combined_client(wms2, req) - eq_(combined.request_template.params.layers, ['foo', 'bar']) - eq_(combined.request_template.url, TESTSERVER_URL + '/service?map=foo') + assert combined.request_template.params.layers == ['foo', 'bar'] + assert combined.request_template.url == TESTSERVER_URL + '/service?map=foo' def test_combine_different_url(self): req1 = WMS111MapRequest(url=TESTSERVER_URL + '/service?map=bar', @@ -325,7 +321,7 @@ def test_transform_fi_request_supported_srs(self): req = WMS111FeatureInfoRequest(url=TESTSERVER_URL + '/service?map=foo', param={'layers':'foo'}) http = MockHTTPClient() - wms = WMSInfoClient(req, http_client=http, supported_srs=[SRS(25832)]) + wms = WMSInfoClient(req, http_client=http, supported_srs=SupportedSRS([SRS(25832)])) fi_req = InfoQuery((8, 50, 9, 51), (512, 512), SRS(4326), (128, 64), 'text/plain') @@ -359,12 +355,12 @@ self.r = WMS100MapRequest(param=dict(layers='foo', version='1.1.1', request='GetMap')) self.r.params = self.r.adapt_params_to_version() def test_version(self): - eq_(self.r.params['WMTVER'], '1.0.0') + assert self.r.params['WMTVER'] == '1.0.0' assert 'VERSION' not in self.r.params def test_service(self): assert 'SERVICE' not in self.r.params def test_request(self): - eq_(self.r.params['request'], 'map') + assert self.r.params['request'] == 'map' def test_str(self): assert_query_eq(str(self.r.params), 'layers=foo&styles=&request=map&wmtver=1.0.0') @@ -373,12 +369,12 @@ self.r = WMS130MapRequest(param=dict(layers='foo', WMTVER='1.0.0')) self.r.params = self.r.adapt_params_to_version() def test_version(self): - eq_(self.r.params['version'], '1.3.0') + assert self.r.params['version'] == '1.3.0' assert 'WMTVER' not in self.r.params def test_service(self): - eq_(self.r.params['service'], 'WMS' ) + assert self.r.params['service'] == 'WMS' def test_request(self): - eq_(self.r.params['request'], 'GetMap') + assert self.r.params['request'] == 'GetMap' def test_str(self): query_eq(str(self.r.params), 'layers=foo&styles=&service=WMS&request=GetMap&version=1.3.0') @@ -387,5 +383,5 @@ self.r = WMS111MapRequest(param=dict(layers='foo', WMTVER='1.0.0')) self.r.params = self.r.adapt_params_to_version() def test_version(self): - eq_(self.r.params['version'], '1.1.1') + assert self.r.params['version'] == '1.1.1' assert 'WMTVER' not in self.r.params diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_collections.py mapproxy-1.12.0/mapproxy/test/unit/test_collections.py --- mapproxy-1.11.0/mapproxy/test/unit/test_collections.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_collections.py 2019-08-30 07:34:08.000000000 +0000 @@ -13,15 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pytest + from mapproxy.util.collections import LRU, ImmutableDictList -from nose.tools import eq_, raises class TestLRU(object): - @raises(KeyError) def test_missing_key(self): lru = LRU(10) - lru['foo'] + with pytest.raises(KeyError): + lru['foo'] def test_contains(self): lru = LRU(10) @@ -40,20 +41,20 @@ lru = LRU(10) lru['foo1'] = 1 lru['foo2'] = 2 - eq_(lru['foo1'], 1) - eq_(lru['foo2'], 2) + assert lru['foo1'] == 1 + assert lru['foo2'] == 2 def test_get(self): lru = LRU(10) lru['foo1'] = 1 - eq_(lru.get('foo1'), 1) - eq_(lru.get('foo1', 2), 1) + assert lru.get('foo1') == 1 + assert lru.get('foo1', 2) == 1 def test_get_default(self): lru = LRU(10) lru['foo1'] = 1 - eq_(lru.get('foo2'), None) - eq_(lru.get('foo2', 2), 2) + assert lru.get('foo2') == None + assert lru.get('foo2', 2) == 2 def test_delitem(self): lru = LRU(10) @@ -80,16 +81,16 @@ def test_length(self): lru = LRU(2) - eq_(len(lru), 0) + assert len(lru) == 0 lru['foo1'] = 1 - eq_(len(lru), 1) + assert len(lru) == 1 lru['foo2'] = 2 - eq_(len(lru), 2) + assert len(lru) == 2 lru['foo3'] = 3 - eq_(len(lru), 2) + assert len(lru) == 2 del lru['foo3'] - eq_(len(lru), 1) + assert len(lru) == 1 class TestImmutableDictList(object): @@ -104,12 +105,12 @@ def test_named_iteritems(self): res = ImmutableDictList([('one', 10), ('two', 5), ('three', 3)]) itr = res.iteritems() - eq_(next(itr), ('one', 10)) - eq_(next(itr), ('two', 5)) - eq_(next(itr), ('three', 3)) + assert next(itr) == ('one', 10) + assert next(itr) == ('two', 5) + assert next(itr) == ('three', 3) try: next(itr) except StopIteration: pass else: - assert False, 'StopIteration expected' \ No newline at end of file + assert False, 'StopIteration expected' diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_concat_legends.py mapproxy-1.12.0/mapproxy/test/unit/test_concat_legends.py --- mapproxy-1.11.0/mapproxy/test/unit/test_concat_legends.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_concat_legends.py 2019-08-30 07:34:08.000000000 +0000 @@ -18,6 +18,7 @@ from mapproxy.image.merge import concat_legends from mapproxy.test.image import is_png + class Test_Concat_Legends(object): def test_concatenation(self): legends = [] diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_config.py mapproxy-1.12.0/mapproxy/test/unit/test_config.py --- mapproxy-1.11.0/mapproxy/test/unit/test_config.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_config.py 2019-08-30 07:34:08.000000000 +0000 @@ -15,9 +15,9 @@ from mapproxy.config import Options, base_config, load_base_config - from mapproxy.test.helper import TempFiles + def teardown_module(): load_base_config(clear_existing=True) diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_conf_loader.py mapproxy-1.12.0/mapproxy/test/unit/test_conf_loader.py --- mapproxy-1.11.0/mapproxy/test/unit/test_conf_loader.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_conf_loader.py 2019-08-30 07:34:08.000000000 +0000 @@ -15,28 +15,32 @@ # limitations under the License. from __future__ import division -import yaml + import time -from mapproxy.srs import SRS + +import yaml +import pytest + +from mapproxy.config.coverage import load_coverage from mapproxy.config.loader import ( ProxyConfiguration, load_configuration, merge_dict, ConfigurationError, ) -from mapproxy.config.coverage import load_coverage -from mapproxy.config.spec import validate_options from mapproxy.cache.tile import TileManager +from mapproxy.config.spec import validate_options from mapproxy.seed.spec import validate_seed_conf +from mapproxy.srs import SRS from mapproxy.test.helper import TempFile from mapproxy.test.unit.test_grid import assert_almost_equal_bbox from mapproxy.util.geom import EmptyGeometryError -from nose.tools import eq_, assert_raises + class TestLayerConfiguration(object): def _test_conf(self, yaml_part): base = {'sources': {'s': {'type': 'wms', 'req': {'url': ''}}}} - base.update(yaml.load(yaml_part)) + base.update(yaml.safe_load(yaml_part)) return base def test_legacy_ordered(self): @@ -52,24 +56,25 @@ title: Layer Three sources: [s] ''') - conf = ProxyConfiguration(conf) - root = conf.wms_root_layer.wms_layer() + with pytest.warns(RuntimeWarning): + conf = ProxyConfiguration(conf) + root = conf.wms_root_layer.wms_layer() # no root layer defined - eq_(root.title, None) - eq_(root.name, None) + assert root.title == None + assert root.name == None layers = root.child_layers() # names are in order - eq_(layers.keys(), ['one', 'two', 'three']) + assert layers.keys() == ['one', 'two', 'three'] - eq_(len(layers), 3) - eq_(layers['one'].title, 'Layer One') - eq_(layers['two'].title, 'Layer Two') - eq_(layers['three'].title, 'Layer Three') + assert len(layers) == 3 + assert layers['one'].title == 'Layer One' + assert layers['two'].title == 'Layer Two' + assert layers['three'].title == 'Layer Three' layers_conf = conf.layers - eq_(len(layers_conf), 3) + assert len(layers_conf) == 3 def test_legacy_unordered(self): conf = self._test_conf(''' @@ -84,21 +89,22 @@ title: Layer Three sources: [s] ''') - conf = ProxyConfiguration(conf) - root = conf.wms_root_layer.wms_layer() + with pytest.warns(RuntimeWarning): + conf = ProxyConfiguration(conf) + root = conf.wms_root_layer.wms_layer() # no root layer defined - eq_(root.title, None) - eq_(root.name, None) + assert root.title == None + assert root.name == None layers = root.child_layers() # names might not be in order # layers.keys() != ['one', 'two', 'three'] - eq_(len(layers), 3) - eq_(layers['one'].title, 'Layer One') - eq_(layers['two'].title, 'Layer Two') - eq_(layers['three'].title, 'Layer Three') + assert len(layers) == 3 + assert layers['one'].title == 'Layer One' + assert layers['two'].title == 'Layer Two' + assert layers['three'].title == 'Layer Three' def test_with_root(self): conf = self._test_conf(''' @@ -116,20 +122,20 @@ conf = ProxyConfiguration(conf) root = conf.wms_root_layer.wms_layer() - eq_(root.title, 'Root Layer') - eq_(root.name, 'root') + assert root.title == 'Root Layer' + assert root.name == 'root' layers = root.child_layers() # names are in order - eq_(layers.keys(), ['root', 'one', 'two']) + assert layers.keys() == ['root', 'one', 'two'] - eq_(len(layers), 3) - eq_(layers['root'].title, 'Root Layer') - eq_(layers['one'].title, 'Layer One') - eq_(layers['two'].title, 'Layer Two') + assert len(layers) == 3 + assert layers['root'].title == 'Root Layer' + assert layers['one'].title == 'Layer One' + assert layers['two'].title == 'Layer Two' layers_conf = conf.layers - eq_(len(layers_conf), 2) + assert len(layers_conf) == 2 def test_with_unnamed_root(self): conf = self._test_conf(''' @@ -146,12 +152,12 @@ conf = ProxyConfiguration(conf) root = conf.wms_root_layer.wms_layer() - eq_(root.title, 'Root Layer') - eq_(root.name, None) + assert root.title == 'Root Layer' + assert root.name == None layers = root.child_layers() # names are in order - eq_(layers.keys(), ['one', 'two']) + assert layers.keys() == ['one', 'two'] def test_without_root(self): conf = self._test_conf(''' @@ -166,12 +172,12 @@ conf = ProxyConfiguration(conf) root = conf.wms_root_layer.wms_layer() - eq_(root.title, None) - eq_(root.name, None) + assert root.title == None + assert root.name == None layers = root.child_layers() # names are in order - eq_(layers.keys(), ['one', 'two']) + assert layers.keys() == ['one', 'two'] def test_hierarchy(self): conf = self._test_conf(''' @@ -200,19 +206,19 @@ conf = ProxyConfiguration(conf) root = conf.wms_root_layer.wms_layer() - eq_(root.title, 'Root Layer') - eq_(root.name, None) + assert root.title == 'Root Layer' + assert root.name == None layers = root.child_layers() # names are in order - eq_(layers.keys(), ['one', 'onea', 'oneb', 'oneba', 'onebb', 'two']) + assert layers.keys() == ['one', 'onea', 'oneb', 'oneba', 'onebb', 'two'] layers_conf = conf.layers - eq_(len(layers_conf), 4) - eq_(layers_conf.keys(), ['onea', 'oneba', 'onebb', 'two']) - eq_(layers_conf['onea'].conf['title'], 'Layer One A') - eq_(layers_conf['onea'].conf['name'], 'onea') - eq_(layers_conf['onea'].conf['sources'], ['s']) + assert len(layers_conf) == 4 + assert layers_conf.keys() == ['onea', 'oneba', 'onebb', 'two'] + assert layers_conf['onea'].conf['title'] == 'Layer One A' + assert layers_conf['onea'].conf['name'] == 'onea' + assert layers_conf['onea'].conf['sources'] == ['s'] def test_hierarchy_root_is_list(self): conf = self._test_conf(''' @@ -229,12 +235,12 @@ conf = ProxyConfiguration(conf) root = conf.wms_root_layer.wms_layer() - eq_(root.title, 'Root Layer') - eq_(root.name, None) + assert root.title == 'Root Layer' + assert root.name == None layers = root.child_layers() # names are in order - eq_(layers.keys(), ['one', 'two']) + assert layers.keys() == ['one', 'two'] def test_without_sources_or_layers(self): conf = self._test_conf(''' @@ -262,17 +268,17 @@ conf = {} conf = ProxyConfiguration(conf) grid = conf.grids['GLOBAL_MERCATOR'].tile_grid() - eq_(grid.srs, SRS(900913)) + assert grid.srs == SRS(900913) grid = conf.grids['GLOBAL_GEODETIC'].tile_grid() - eq_(grid.srs, SRS(4326)) + assert grid.srs == SRS(4326) def test_simple(self): conf = {'grids': {'grid': {'srs': 'EPSG:4326', 'bbox': [5, 50, 10, 55]}}} conf = ProxyConfiguration(conf) grid = conf.grids['grid'].tile_grid() - eq_(grid.srs, SRS(4326)) + assert grid.srs == SRS(4326) def test_with_base(self): conf = {'grids': { @@ -281,35 +287,35 @@ }} conf = ProxyConfiguration(conf) grid = conf.grids['grid'].tile_grid() - eq_(grid.srs, SRS(4326)) + assert grid.srs == SRS(4326) def test_with_num_levels(self): conf = {'grids': {'grid': {'srs': 'EPSG:4326', 'bbox': [5, 50, 10, 55], 'num_levels': 8}}} conf = ProxyConfiguration(conf) grid = conf.grids['grid'].tile_grid() - eq_(len(grid.resolutions), 8) + assert len(grid.resolutions) == 8 def test_with_bbox_srs(self): conf = {'grids': {'grid': {'srs': 'EPSG:25832', 'bbox': [5, 50, 10, 55], 'bbox_srs': 'EPSG:4326'}}} conf = ProxyConfiguration(conf) grid = conf.grids['grid'].tile_grid() - assert_almost_equal_bbox([213372, 5538660, 571666, 6102110], grid.bbox, -3) + assert_almost_equal_bbox([213372, 5538660, 571666, 6102110], grid.bbox, 1) def test_with_min_res(self): conf = {'grids': {'grid': {'srs': 'EPSG:4326', 'bbox': [5, 50, 10, 55], 'min_res': 0.0390625}}} conf = ProxyConfiguration(conf) grid = conf.grids['grid'].tile_grid() - assert_almost_equal_bbox([5, 50, 10, 55], grid.bbox, 2) - eq_(grid.resolution(0), 0.0390625) - eq_(grid.resolution(1), 0.01953125) + assert_almost_equal_bbox([5, 50, 10, 55], grid.bbox) + assert grid.resolution(0) == 0.0390625 + assert grid.resolution(1) == 0.01953125 def test_with_max_res(self): conf = {'grids': {'grid': {'srs': 'EPSG:4326', 'bbox': [5, 50, 10, 55], 'max_res': 0.0048828125}}} conf = ProxyConfiguration(conf) grid = conf.grids['grid'].tile_grid() - assert_almost_equal_bbox([5, 50, 10, 55], grid.bbox, 2) - eq_(grid.resolution(0), 0.01953125) - eq_(grid.resolution(1), 0.01953125/2) + assert_almost_equal_bbox([5, 50, 10, 55], grid.bbox) + assert grid.resolution(0) == 0.01953125 + assert grid.resolution(1) == 0.01953125/2 class TestWMSSourceConfiguration(object): def test_simple_grid(self): @@ -337,21 +343,21 @@ conf = ProxyConfiguration(conf_dict) caches = conf.caches['osm'].caches() - eq_(len(caches), 1) + assert len(caches) == 1 grid, extent, manager = caches[0] - eq_(grid.srs, SRS(4326)) - eq_(grid.bbox, (5.0, 50.0, 10.0, 55.0)) + assert grid.srs == SRS(4326) + assert grid.bbox == (5.0, 50.0, 10.0, 55.0) assert isinstance(manager, TileManager) def check_source_layers(self, conf_dict, layers): conf = ProxyConfiguration(conf_dict) caches = conf.caches['osm'].caches() - eq_(len(caches), 1) + assert len(caches) == 1 grid, extent, manager = caches[0] source_layers = manager.sources[0].client.request_template.params.layers - eq_(source_layers, layers) + assert source_layers == layers def test_tagged_source(self): conf_dict = { @@ -464,7 +470,7 @@ conf = ProxyConfiguration(conf_dict) wms_layer = conf.layers['osm'].wms_layer() layers = wms_layer.map_layers[0].client.request_template.params.layers - eq_(layers, ['base', 'roads']) + assert layers == ['base', 'roads'] def test_tagged_source_encoding(self): conf_dict = { @@ -494,7 +500,7 @@ conf = ProxyConfiguration(conf_dict) wms_layer = conf.layers['osm'].wms_layer() layers = wms_layer.map_layers[0].client.request_template.params.layers - eq_(layers, [u'☃']) + assert layers == [u'☃'] # from cache self.check_source_layers(conf_dict, [u'☃']) @@ -531,7 +537,7 @@ } } errors, informal_only = validate_options(conf_dict) - eq_(len(errors), 1) + assert len(errors) == 1 assert "unknown 'f' in caches" in errors[0] def test_no_band_cache(self): @@ -544,7 +550,7 @@ } } errors, informal_only = validate_options(conf_dict) - eq_(len(errors), 1) + assert len(errors) == 1 assert "missing 'band', not in caches" in errors[0], errors @@ -572,16 +578,18 @@ """ def test_loading(self): - with TempFile() as f: - open(f, 'wb').write(self.yaml_string) - services = load_services(f) + with TempFile() as tf: + with open(tf, 'wb') as f: + f.write(self.yaml_string) + services = load_services(tf) assert 'service' in services[0].names def test_loading_broken_yaml(self): - with TempFile() as f: - open(f, 'wb').write(b'\tbroken:foo') + with TempFile() as tf: + with open(tf, 'wb') as f: + f.write(b'\tbroken:foo') try: - load_services(f) + load_services(tf) except ConfigurationError: pass else: @@ -640,14 +648,14 @@ config = load_configuration(cfg) http = config.globals.get_value('http') - eq_(http['client_timeout'], 1) - eq_(http['headers']['bar'], 'qux') - eq_(http['headers']['foo'], 'bar') - eq_(http['headers']['baz'], 'quux') - eq_(http['method'], 'GET') + assert http['client_timeout'] == 1 + assert http['headers']['bar'] == 'qux' + assert http['headers']['foo'] == 'bar' + assert http['headers']['baz'] == 'quux' + assert http['method'] == 'GET' config_files = config.config_files() - eq_(set(config_files.keys()), set([gp, p, cfg])) + assert set(config_files.keys()) == set([gp, p, cfg]) assert abs(config_files[gp] - time.time()) < 10 assert abs(config_files[p] - time.time()) < 10 assert abs(config_files[cfg] - time.time()) < 10 @@ -658,25 +666,25 @@ a = {'a': 1, 'b': [12, 13]} b = {} m = merge_dict(a, b) - eq_(a, m) + assert a == m def test_empty_conf(self): a = {} b = {'a': 1, 'b': [12, 13]} m = merge_dict(a, b) - eq_(b, m) + assert b == m def test_differ(self): a = {'a': 12} b = {'b': 42} m = merge_dict(a, b) - eq_({'a': 12, 'b': 42}, m) + assert {'a': 12 , 'b': 42} == m def test_recursive(self): a = {'a': {'aa': 12, 'a':{'aaa': 100}}} b = {'a': {'aa': 11, 'ab': 13, 'a':{'aaa': 101, 'aab': 101}}} m = merge_dict(a, b) - eq_({'a': {'aa': 12, 'ab': 13, 'a':{'aaa': 100, 'aab': 101}}}, m) + assert {'a': {'aa': 12, 'ab': 13, 'a':{'aaa': 100, 'aab': 101}}} == m class TestLoadConfiguration(object): @@ -688,7 +696,8 @@ """) load_configuration(f) # defaults to ignore_warnings=True - assert_raises(ConfigurationError, load_configuration, f, ignore_warnings=False) + with pytest.raises(ConfigurationError): + load_configuration(f, ignore_warnings=False) class TestImageOptions(object): @@ -697,21 +706,21 @@ } conf = ProxyConfiguration(conf_dict) image_opts = conf.globals.image_options.image_opts({}, 'image/png') - eq_(image_opts.format, 'image/png') - eq_(image_opts.mode, None) - eq_(image_opts.colors, 256) - eq_(image_opts.transparent, None) - eq_(image_opts.resampling, 'bicubic') + assert image_opts.format == 'image/png' + assert image_opts.mode == None + assert image_opts.colors == 256 + assert image_opts.transparent == None + assert image_opts.resampling == 'bicubic' def test_default_format_paletted_false(self): conf_dict = {'globals': {'image': { 'paletted': False }}} conf = ProxyConfiguration(conf_dict) image_opts = conf.globals.image_options.image_opts({}, 'image/png') - eq_(image_opts.format, 'image/png') - eq_(image_opts.mode, None) - eq_(image_opts.colors, None) - eq_(image_opts.transparent, None) - eq_(image_opts.resampling, 'bicubic') + assert image_opts.format == 'image/png' + assert image_opts.mode == None + assert image_opts.colors == None + assert image_opts.transparent == None + assert image_opts.resampling == 'bicubic' def test_update_default_format(self): conf_dict = {'globals': {'image': {'formats': { @@ -720,12 +729,12 @@ }}}} conf = ProxyConfiguration(conf_dict) image_opts = conf.globals.image_options.image_opts({}, 'image/png') - eq_(image_opts.format, 'image/png') - eq_(image_opts.mode, None) - eq_(image_opts.colors, 16) - eq_(image_opts.transparent, None) - eq_(image_opts.resampling, 'nearest') - eq_(image_opts.encoding_options['quantizer'], 'mediancut') + assert image_opts.format == 'image/png' + assert image_opts.mode == None + assert image_opts.colors == 16 + assert image_opts.transparent == None + assert image_opts.resampling == 'nearest' + assert image_opts.encoding_options['quantizer'] == 'mediancut' def test_custom_format(self): conf_dict = {'globals': {'image': {'resampling_method': 'bilinear', @@ -735,11 +744,11 @@ }}} conf = ProxyConfiguration(conf_dict) image_opts = conf.globals.image_options.image_opts({}, 'image/foo') - eq_(image_opts.format, 'image/foo') - eq_(image_opts.mode, 'RGBA') - eq_(image_opts.colors, 42) - eq_(image_opts.transparent, None) - eq_(image_opts.resampling, 'bilinear') + assert image_opts.format == 'image/foo' + assert image_opts.mode == 'RGBA' + assert image_opts.colors == 42 + assert image_opts.transparent == None + assert image_opts.resampling == 'bilinear' def test_format_grid(self): conf_dict = { @@ -758,11 +767,11 @@ } conf = ProxyConfiguration(conf_dict) image_opts = conf.caches['test'].image_opts() - eq_(image_opts.format, 'image/png') - eq_(image_opts.mode, None) - eq_(image_opts.colors, 256) - eq_(image_opts.transparent, None) - eq_(image_opts.resampling, 'bilinear') + assert image_opts.format == 'image/png' + assert image_opts.mode == None + assert image_opts.colors == 256 + assert image_opts.transparent == None + assert image_opts.resampling == 'bilinear' def test_custom_format_grid(self): conf_dict = { @@ -796,18 +805,18 @@ } conf = ProxyConfiguration(conf_dict) image_opts = conf.caches['test'].image_opts() - eq_(image_opts.format, 'image/png') - eq_(image_opts.mode, 'P') - eq_(image_opts.colors, 16) - eq_(image_opts.transparent, None) - eq_(image_opts.resampling, 'bilinear') + assert image_opts.format == 'image/png' + assert image_opts.mode == 'P' + assert image_opts.colors == 16 + assert image_opts.transparent == None + assert image_opts.resampling == 'bilinear' image_opts = conf.caches['test2'].image_opts() - eq_(image_opts.format, 'image/png') - eq_(image_opts.mode, 'RGBA') - eq_(image_opts.colors, 8) - eq_(image_opts.transparent, True) - eq_(image_opts.resampling, 'bilinear') + assert image_opts.format == 'image/png' + assert image_opts.mode == 'RGBA' + assert image_opts.colors == 8 + assert image_opts.transparent == True + assert image_opts.resampling == 'bilinear' def test_custom_format_source(self): conf_dict = { @@ -843,36 +852,36 @@ conf = ProxyConfiguration(conf_dict) _grid, _extent, tile_mgr = conf.caches['test'].caches()[0] image_opts = tile_mgr.image_opts - eq_(image_opts.format, 'image/png') - eq_(image_opts.mode, 'P') - eq_(image_opts.colors, 16) - eq_(image_opts.transparent, None) - eq_(image_opts.resampling, 'bilinear') + assert image_opts.format == 'image/png' + assert image_opts.mode == 'P' + assert image_opts.colors == 16 + assert image_opts.transparent == None + assert image_opts.resampling == 'bilinear' image_opts = tile_mgr.sources[0].image_opts - eq_(image_opts.format, 'image/png') - eq_(image_opts.mode, 'P') - eq_(image_opts.colors, 256) - eq_(image_opts.transparent, None) - eq_(image_opts.resampling, 'bilinear') + assert image_opts.format == 'image/png' + assert image_opts.mode == 'P' + assert image_opts.colors == 256 + assert image_opts.transparent == None + assert image_opts.resampling == 'bilinear' conf_dict['caches']['test']['request_format'] = 'image/tiff' conf = ProxyConfiguration(conf_dict) _grid, _extent, tile_mgr = conf.caches['test'].caches()[0] image_opts = tile_mgr.image_opts - eq_(image_opts.format, 'image/png') - eq_(image_opts.mode, 'P') - eq_(image_opts.colors, 16) - eq_(image_opts.transparent, None) - eq_(image_opts.resampling, 'bilinear') + assert image_opts.format == 'image/png' + assert image_opts.mode == 'P' + assert image_opts.colors == 16 + assert image_opts.transparent == None + assert image_opts.resampling == 'bilinear' image_opts = tile_mgr.sources[0].image_opts - eq_(image_opts.format, 'image/tiff') - eq_(image_opts.mode, None) - eq_(image_opts.colors, None) - eq_(image_opts.transparent, None) - eq_(image_opts.resampling, 'bilinear') + assert image_opts.format == 'image/tiff' + assert image_opts.mode == None + assert image_opts.colors == None + assert image_opts.transparent == None + assert image_opts.resampling == 'bilinear' def test_encoding_options_errors(self): conf_dict = { @@ -941,7 +950,7 @@ errors, informal_only = validate_seed_conf(conf) assert informal_only assert len(errors) == 1 - eq_(errors[0], "unknown 'unknown' in coverages.covname.union[1]") + assert errors[0] == "unknown 'unknown' in coverages.covname.union[1]" class TestLoadCoverage(object): @@ -950,11 +959,13 @@ with open(tf, 'wb') as f: f.write(b'{"type": "FeatureCollection", "features": []}') conf = {'datasource': tf, 'srs': 'EPSG:4326'} - assert_raises(EmptyGeometryError, load_coverage, conf) + with pytest.raises(EmptyGeometryError): + load_coverage(conf) def test_load_empty_geojson_ogr(self): with TempFile() as tf: with open(tf, 'wb') as f: f.write(b'{"type": "FeatureCollection", "features": []}') conf = {'datasource': tf, 'where': '0 != 1', 'srs': 'EPSG:4326'} - assert_raises(EmptyGeometryError, load_coverage, conf) + with pytest.raises(EmptyGeometryError): + load_coverage(conf) diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_conf_validator.py mapproxy-1.12.0/mapproxy/test/unit/test_conf_validator.py --- mapproxy-1.11.0/mapproxy/test/unit/test_conf_validator.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_conf_validator.py 2019-08-30 07:34:08.000000000 +0000 @@ -14,16 +14,15 @@ # limitations under the License. from __future__ import print_function + import yaml from mapproxy.config.validator import validate_references -from nose.tools import eq_ - class TestValidator(object): def _test_conf(self, yaml_part=None): - base = yaml.load(''' + base = yaml.safe_load(''' services: wms: md: @@ -44,23 +43,23 @@ layers: one ''') if yaml_part is not None: - base.update(yaml.load(yaml_part)) + base.update(yaml.safe_load(yaml_part)) return base def test_valid_config(self): conf = self._test_conf() errors = validate_references(conf) - eq_(errors, []) + assert errors == [] def test_missing_layer_source(self): conf = self._test_conf() del conf['caches']['one_cache'] errors = validate_references(conf) - eq_(errors, [ + assert errors == [ "Source 'one_cache' for layer 'one' not in cache or source section" - ]) + ] def test_empty_layer_sources(self): conf = self._test_conf(''' @@ -71,35 +70,35 @@ ''') errors = validate_references(conf) - eq_(errors, [ + assert errors == [ "Missing sources for layer 'one'" - ]) + ] def test_missing_cache_source(self): conf = self._test_conf() del conf['sources']['one_source'] errors = validate_references(conf) - eq_(errors, [ + assert errors == [ "Source 'one_source' for cache 'one_cache' not found in config" - ]) + ] def test_missing_layers_section(self): conf = self._test_conf() del conf['layers'] errors = validate_references(conf) - eq_(errors, [ + assert errors == [ 'Missing layers section' - ]) + ] def test_missing_services_section(self): conf = self._test_conf() del conf['services'] errors = validate_references(conf) - eq_(errors, [ + assert errors == [ 'Missing services section' - ]) + ] def test_tile_source(self): conf = self._test_conf(''' @@ -109,9 +108,9 @@ ''') errors = validate_references(conf) - eq_(errors, [ + assert errors == [ "Tile source 'missing' for layer 'one' not in cache section" - ]) + ] def test_missing_grid(self): conf = self._test_conf(''' @@ -124,9 +123,9 @@ ''') errors = validate_references(conf) - eq_(errors, [ + assert errors == [ "Grid 'MYGRID_OTHERGRID' for cache 'one_cache' not found in config" - ]) + ] def test_misconfigured_wms_source(self): conf = self._test_conf() @@ -134,9 +133,9 @@ del conf['sources']['one_source']['req']['layers'] errors = validate_references(conf) - eq_(errors, [ + assert errors == [ "Missing 'layers' for source 'one_source'" - ]) + ] def test_misconfigured_mapserver_source_without_globals(self): conf = self._test_conf(''' @@ -150,23 +149,23 @@ ''') errors = validate_references(conf) - eq_(errors, [ + assert errors == [ 'Could not find mapserver binary (/foo/bar/baz)' - ]) + ] del conf['sources']['one_source']['mapserver']['binary'] errors = validate_references(conf) - eq_(errors, [ + assert errors == [ "Missing mapserver binary for source 'one_source'" - ]) + ] del conf['sources']['one_source']['mapserver'] errors = validate_references(conf) - eq_(errors, [ + assert errors == [ "Missing mapserver binary for source 'one_source'" - ]) + ] def test_misconfigured_mapserver_source_with_globals(self): conf = self._test_conf(''' @@ -181,16 +180,16 @@ ''') errors = validate_references(conf) - eq_(errors, [ + assert errors == [ 'Could not find mapserver binary (/foo/bar/baz)' - ]) + ] del conf['globals']['mapserver']['binary'] errors = validate_references(conf) - eq_(errors, [ + assert errors == [ "Missing mapserver binary for source 'one_source'" - ]) + ] def test_tagged_sources_with_layers(self): conf = self._test_conf(''' @@ -201,10 +200,10 @@ ''') errors = validate_references(conf) - eq_(errors, [ + assert errors == [ "Supported layers for source 'one_source' are 'one' but tagged source " "requested layers 'foo, bar'" - ]) + ] def test_tagged_source_without_layers(self): conf = self._test_conf(''' @@ -217,7 +216,7 @@ del conf['sources']['one_source']['req']['layers'] errors = validate_references(conf) - eq_(errors, []) + assert errors == [] def test_tagged_source_with_colons(self): conf = self._test_conf(''' @@ -230,7 +229,7 @@ del conf['sources']['one_source']['req']['layers'] errors = validate_references(conf) - eq_(errors, []) + assert errors == [] def test_with_grouped_layer(self): conf = self._test_conf(''' @@ -244,7 +243,7 @@ ''') errors = validate_references(conf) - eq_(errors, []) + assert errors == [] def test_without_cache(self): conf = self._test_conf(''' @@ -255,7 +254,7 @@ ''') errors = validate_references(conf) - eq_(errors, []) + assert errors == [] def test_mapserver_with_tagged_layers(self): conf = self._test_conf(''' @@ -274,11 +273,11 @@ ''') errors = validate_references(conf) - eq_(errors, [ + assert errors == [ 'Could not find mapserver binary (/foo/bar/baz)', "Supported layers for source 'one_source' are 'one' but tagged source " "requested layers 'foo, bar'" - ]) + ] def test_mapnik_with_tagged_layers(self): conf = self._test_conf(''' @@ -294,10 +293,10 @@ ''') errors = validate_references(conf) - eq_(errors, [ + assert errors == [ "Supported layers for source 'one_source' are 'one' but tagged source " "requested layers 'foo, bar'" - ]) + ] def test_tagged_layers_for_unsupported_source_type(self): conf = self._test_conf(''' @@ -312,10 +311,10 @@ ''') errors = validate_references(conf) - eq_(errors, [ + assert errors == [ "Found tagged source 'one_source' in cache 'one_cache' but tagged sources " "only supported for 'wms, mapserver, mapnik' sources" - ]) + ] def test_cascaded_caches(self): conf = self._test_conf(''' @@ -328,7 +327,7 @@ ''') errors = validate_references(conf) - eq_(errors, []) + assert errors == [] def test_with_int_0_as_names_and_layers(self): conf = self._test_conf(''' @@ -353,7 +352,7 @@ ''') errors = validate_references(conf) - eq_(errors, []) + assert errors == [] def test_band_merge_missing_source(self): conf = self._test_conf(''' @@ -384,7 +383,7 @@ ''') errors = validate_references(conf) - eq_(errors, [ + assert errors == [ "Source 'missing1' for cache 'one_cache' not found in config", "Source 'missing2' for cache 'cache_missing_source' not found in config", - ]) + ] diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_decorate_img.py mapproxy-1.12.0/mapproxy/test/unit/test_decorate_img.py --- mapproxy-1.11.0/mapproxy/test/unit/test_decorate_img.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_decorate_img.py 2019-08-30 07:34:08.000000000 +0000 @@ -1,16 +1,30 @@ +# This file is part of the MapProxy project. +# Copyright (C) 2010 Omniscale +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from mapproxy.compat.image import Image from mapproxy.grid import tile_grid from mapproxy.image import BlankImageSource from mapproxy.image import ImageSource from mapproxy.image.opts import ImageOptions from mapproxy.layer import MapLayer, DefaultMapExtent -from mapproxy.compat.image import Image from mapproxy.service.base import Server from mapproxy.service.tile import TileServer from mapproxy.service.wms import WMSGroupLayer, WMSServer from mapproxy.service.wmts import WMTSServer from mapproxy.test.http import make_wsgi_env from mapproxy.util.ext.odict import odict -from nose.tools import eq_ class DummyLayer(MapLayer): @@ -81,7 +95,7 @@ img_src1, 'wms.map', ['layer1'], env, self.query_extent ) - eq_(img_src1, img_src2) + assert img_src1 == img_src2 def test_returns_imagesource(self): img_src1 = ImageSource(Image.new('RGBA', (100, 100))) @@ -104,7 +118,7 @@ img_src1, 'wms.map', ['layer1'], env, self.query_extent ) - eq_(self.called, True) + assert self.called == True def return_new_imagesource_callback(self, img_src, service, layers, **kw): new_img_src = ImageSource(Image.new('RGBA', (100, 100))) @@ -119,7 +133,7 @@ img_src1, 'wms.map', ['layer1'], env, self.query_extent ) - eq_(img_src2, self.new_img_src) + assert img_src2 == self.new_img_src def test_wms_server(self): ''' Test that the decorate_img method is available on a WMSServer instance ''' @@ -130,7 +144,7 @@ img_src1, 'wms.map', ['layer1'], env, self.query_extent ) - eq_(self.called, True) + assert self.called == True def test_tile_server(self): ''' Test that the decorate_img method is available on a TileServer instance ''' @@ -141,7 +155,7 @@ img_src1, 'tms', ['layer1'], env, self.query_extent ) - eq_(self.called, True) + assert self.called == True def test_wmts_server(self): ''' Test that the decorate_img method is available on a WMTSServer instance ''' @@ -152,12 +166,12 @@ img_src1, 'wmts', ['layer1'], env, self.query_extent ) - eq_(self.called, True) + assert self.called == True def test_args(self): def callback(img_src, service, layers, environ, query_extent, **kw): assert isinstance(img_src, ImageSource) - eq_('wms.map', service) + assert 'wms.map' == service assert isinstance(layers, list) assert isinstance(environ, dict) assert len(query_extent) == 2 diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_exceptions.py mapproxy-1.12.0/mapproxy/test/unit/test_exceptions.py --- mapproxy-1.11.0/mapproxy/test/unit/test_exceptions.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_exceptions.py 2019-08-30 07:34:08.000000000 +0000 @@ -13,16 +13,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -from mapproxy.compat.image import Image from io import BytesIO + +from mapproxy.compat.image import Image +from mapproxy.exception import RequestError +from mapproxy.request import url_decode +from mapproxy.request.wms import WMSMapRequest +from mapproxy.request.wms.exception import ( + WMS100ExceptionHandler, + WMS111ExceptionHandler, + WMS130ExceptionHandler, + WMS110ExceptionHandler, +) from mapproxy.test.helper import Mocker, validate_with_dtd, validate_with_xsd from mapproxy.test.image import is_png -from mapproxy.request.wms import WMSMapRequest -from mapproxy.request import url_decode -from mapproxy.exception import RequestError -from mapproxy.request.wms.exception import (WMS100ExceptionHandler, WMS111ExceptionHandler, - WMS130ExceptionHandler, WMS110ExceptionHandler) -from nose.tools import eq_ class ExceptionHandlerTest(Mocker): @@ -108,7 +112,7 @@ the exception message """ - eq_(expected_resp.strip(), response.data) + assert expected_resp.strip() == response.data assert validate_with_dtd(response.data, 'wms/1.1.0/exception_1_1_0.dtd') class TestWMS130ExceptionHandler(Mocker): @@ -202,10 +206,9 @@ assert is_png(data) img = Image.open(data) assert img.size == (150, 100) - eq_(sorted([x for x in img.histogram() if x > 25]), - [377, 14623]) + assert sorted([x for x in img.histogram() if x > 25]) == [377, 14623] img = img.convert('RGBA') - eq_(img.getpixel((0, 0))[3], 0) + assert img.getpixel((0, 0))[3] == 0 class TestWMSBlankExceptionHandler(ExceptionHandlerTest): @@ -220,8 +223,8 @@ assert is_png(data) img = Image.open(data) assert img.size == (150, 100) - eq_(img.getpixel((0, 0)), 0) #pallete image - eq_(img.getpalette()[0:3], [255, 255, 255]) + assert img.getpixel((0, 0)) == 0 #pallete image + assert img.getpalette()[0:3] == [255, 255, 255] def test_exception_w_bgcolor(self): self.req.set('exceptions', 'blank') self.req.set('bgcolor', '0xff00ff') @@ -235,8 +238,8 @@ assert is_png(data) img = Image.open(data) assert img.size == (150, 100) - eq_(img.getpixel((0, 0)), 0) #pallete image - eq_(img.getpalette()[0:3], [255, 0, 255]) + assert img.getpixel((0, 0)) == 0 #pallete image + assert img.getpalette()[0:3] == [255, 0, 255] def test_exception_w_transparent(self): self.req.set('exceptions', 'blank') self.req.set('transparent', 'true' ) @@ -252,4 +255,4 @@ assert img.size == (150, 100) assert img.mode == 'P' img = img.convert('RGBA') - eq_(img.getpixel((0, 0))[3], 0) + assert img.getpixel((0, 0))[3] == 0 diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_featureinfo.py mapproxy-1.12.0/mapproxy/test/unit/test_featureinfo.py --- mapproxy-1.11.0/mapproxy/test/unit/test_featureinfo.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_featureinfo.py 2019-08-30 07:34:08.000000000 +0000 @@ -13,35 +13,29 @@ # See the License for the specific language governing permissions and # limitations under the License. - import os import tempfile from lxml import etree, html -from nose.tools import eq_ from mapproxy.featureinfo import ( - combined_inputs, XSLTransformer, + TextFeatureInfoDoc, XMLFeatureInfoDoc, HTMLFeatureInfoDoc, JSONFeatureInfoDoc, + combine_docs, ) from mapproxy.test.helper import strip_whitespace -def test_combined_inputs(): - foo = 'foo' - bar = 'bar' - - result = combined_inputs([foo, bar]) - result = etree.tostring(result) - eq_(result, b'foobar') - class TestXSLTransformer(object): + def setup(self): - fd_, self.xsl_script = tempfile.mkstemp('.xsl') - xsl = b""" + fd, self.xsl_script = tempfile.mkstemp(".xsl") + os.close(fd) + xsl = ( + b""" @@ -53,144 +47,233 @@ """.strip() - open(self.xsl_script, 'wb').write(xsl) + ) + with open(self.xsl_script, "wb") as f: + f.write(xsl) def teardown(self): os.remove(self.xsl_script) def test_transformer(self): t = XSLTransformer(self.xsl_script) - doc = t.transform(XMLFeatureInfoDoc('Text')) - eq_(strip_whitespace(doc.as_string()), b'Text') + doc = t.transform(XMLFeatureInfoDoc("Text")) + assert strip_whitespace(doc.as_string()) == b"Text" def test_multiple(self): t = XSLTransformer(self.xsl_script) - doc = t.transform(XMLFeatureInfoDoc.combine([ - XMLFeatureInfoDoc(x) for x in - [b'ab', - b'ab1ab2ab3', - b'ab1acab2', - ]])) - eq_(strip_whitespace(doc.as_string()), - strip_whitespace(b''' + doc = t.transform( + XMLFeatureInfoDoc.combine( + [ + XMLFeatureInfoDoc(x) + for x in [ + b"ab", + b"ab1ab2ab3", + b"ab1acab2", + ] + ] + ) + ) + assert strip_whitespace(doc.as_string()) == strip_whitespace( + b""" ab ab1ab2ab3 ab1ab2 - ''')) - eq_(doc.info_type, 'xml') +
""" + ) + assert doc.info_type == "xml" class TestXMLFeatureInfoDocs(object): + def test_as_string(self): - input_tree = etree.fromstring('') + input_tree = etree.fromstring("") doc = XMLFeatureInfoDoc(input_tree) - eq_(strip_whitespace(doc.as_string()), - b'') + assert strip_whitespace(doc.as_string()) == b"" def test_as_etree(self): - doc = XMLFeatureInfoDoc('hello') - eq_(doc.as_etree().getroot().text, 'hello') + doc = XMLFeatureInfoDoc("hello") + assert doc.as_etree().getroot().text == "hello" def test_combine(self): docs = [ - XMLFeatureInfoDoc('foo'), - XMLFeatureInfoDoc('bar'), - XMLFeatureInfoDoc('baz'), + XMLFeatureInfoDoc("foo"), + XMLFeatureInfoDoc("bar"), + XMLFeatureInfoDoc("baz"), ] result = XMLFeatureInfoDoc.combine(docs) - eq_(strip_whitespace(result.as_string()), - strip_whitespace(b'foobarbaz')) - eq_(result.info_type, 'xml') + assert strip_whitespace(result.as_string()) == strip_whitespace( + b"foobarbaz" + ) + assert result.info_type == "xml" class TestXMLFeatureInfoDocsNoLXML(object): + def setup(self): from mapproxy import featureinfo + self.old_etree = featureinfo.etree featureinfo.etree = None + def teardown(self): from mapproxy import featureinfo + featureinfo.etree = self.old_etree def test_combine(self): docs = [ - XMLFeatureInfoDoc(b'foo'), - XMLFeatureInfoDoc(b'bar'), - XMLFeatureInfoDoc(b'baz'), + XMLFeatureInfoDoc(b"foo"), + XMLFeatureInfoDoc(b"bar"), + XMLFeatureInfoDoc(b"baz"), ] result = XMLFeatureInfoDoc.combine(docs) - eq_(b'foo\nbar\nbaz', - result.as_string()) - eq_(result.info_type, 'text') + assert ( + b"foo\nbar\nbaz" + == result.as_string() + ) + assert result.info_type == "text" + class TestHTMLFeatureInfoDocs(object): + def test_as_string(self): - input_tree = html.fromstring('

Foo') + input_tree = html.fromstring("

Foo") doc = HTMLFeatureInfoDoc(input_tree) - assert b'

Foo

' in strip_whitespace(doc.as_string()) + assert b"

Foo

" in strip_whitespace(doc.as_string()) def test_as_etree(self): - doc = HTMLFeatureInfoDoc('

hello

') - eq_(doc.as_etree().find('body/p').text, 'hello') + doc = HTMLFeatureInfoDoc("

hello

") + assert doc.as_etree().find("body/p").text == "hello" def test_combine(self): docs = [ - HTMLFeatureInfoDoc(b'Hello<body><p>baz</p><p>baz2'), - HTMLFeatureInfoDoc(b'<p>foo</p>'), - HTMLFeatureInfoDoc(b'<body><p>bar</p></body>'), + HTMLFeatureInfoDoc(b"<html><head><title>Hello<body><p>baz</p><p>baz2"), + HTMLFeatureInfoDoc(b"<p>foo</p>"), + HTMLFeatureInfoDoc(b"<body><p>bar</p></body>"), ] result = HTMLFeatureInfoDoc.combine(docs) - assert b'<title>Hello' in result.as_string() - assert (b'

baz

baz2

foo

bar

' in - result.as_string()) - eq_(result.info_type, 'html') + assert b"Hello" in result.as_string() + assert ( + b"

baz

baz2

foo

bar

" + in result.as_string() + ) + assert result.info_type == "html" def test_combine_parts(self): docs = [ - HTMLFeatureInfoDoc('

foo

'), - HTMLFeatureInfoDoc('

bar

'), - HTMLFeatureInfoDoc('Hello<body><p>baz</p><p>baz2'), + HTMLFeatureInfoDoc("<p>foo</p>"), + HTMLFeatureInfoDoc("<body><p>bar</p></body>"), + HTMLFeatureInfoDoc("<html><head><title>Hello<body><p>baz</p><p>baz2"), ] result = HTMLFeatureInfoDoc.combine(docs) - assert (b'<body><p>foo</p><p>bar</p><p>baz</p><p>baz2</p></body>' in - result.as_string()) - eq_(result.info_type, 'html') + assert ( + b"<body><p>foo</p><p>bar</p><p>baz</p><p>baz2</p></body>" + in result.as_string() + ) + assert result.info_type == "html" + class TestHTMLFeatureInfoDocsNoLXML(object): + def setup(self): from mapproxy import featureinfo + self.old_etree = featureinfo.etree featureinfo.etree = None + def teardown(self): from mapproxy import featureinfo + featureinfo.etree = self.old_etree def test_combine(self): docs = [ - HTMLFeatureInfoDoc(b'<html><head><title>Hello<body><p>baz</p><p>baz2'), - HTMLFeatureInfoDoc(b'<p>foo</p>'), - HTMLFeatureInfoDoc(b'<body><p>bar</p></body>'), + HTMLFeatureInfoDoc(b"<html><head><title>Hello<body><p>baz</p><p>baz2"), + HTMLFeatureInfoDoc(b"<p>foo</p>"), + HTMLFeatureInfoDoc(b"<body><p>bar</p></body>"), ] result = HTMLFeatureInfoDoc.combine(docs) - eq_(b"<html><head><title>Hello<body><p>baz</p>" - b"<p>baz2\n<p>foo</p>\n<body><p>bar</p></body>", - result.as_string()) - eq_(result.info_type, 'text') + assert ( + b"<html><head><title>Hello<body><p>baz</p>" + b"<p>baz2\n<p>foo</p>\n<body><p>bar</p></body>" == result.as_string() + ) + assert result.info_type == "text" + class TestJSONFeatureInfoDocs(object): + def test_combine(self): docs = [ - JSONFeatureInfoDoc('{}'), + JSONFeatureInfoDoc("{}"), JSONFeatureInfoDoc('{"results": [{"foo": 1}]}'), JSONFeatureInfoDoc('{"results": [{"bar": 2}]}'), ] result = JSONFeatureInfoDoc.combine(docs) - eq_('''{"results": [{"foo": 1}, {"bar": 2}]}''', - result.as_string()) - eq_(result.info_type, 'json') + assert """{"results": [{"foo": 1}, {"bar": 2}]}""" == result.as_string() + assert result.info_type == "json" + + +class TestCombineDocs(object): + + def test_combine_json(self): + docs = [ + JSONFeatureInfoDoc("{}"), + JSONFeatureInfoDoc('{"results": [{"foo": 1}]}'), + JSONFeatureInfoDoc('{"results": [{"bar": 2}]}'), + ] + result, infotype = combine_docs(docs, None) + + assert """{"results": [{"foo": 1}, {"bar": 2}]}""" == result + assert infotype == "json" + + def test_combine_xml(self): + docs = [ + XMLFeatureInfoDoc("<root><a>foo</a></root>"), + XMLFeatureInfoDoc("<root><b>bar</b></root>"), + ] + result, infotype = combine_docs(docs, None) + + assert b"""<root><a>foo</a><b>bar</b></root>""" == result + assert infotype == "xml" + + def test_combine_transform_xml(self, tmpdir): + xsl = tmpdir.join("transform.xsl") + xsl.write( + """<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + <xsl:template match="/"> + <root> + <xsl:apply-templates select='/a/b'/> + </root> + </xsl:template> + <xsl:template match="/a/b"> + <foo><xsl:value-of select="text()" /></foo> + </xsl:template> + </xsl:stylesheet> + """ + ) + transf = XSLTransformer(xsl.strpath) + docs = [ + XMLFeatureInfoDoc("<a><b>foo1</b></a>"), + XMLFeatureInfoDoc("<a><b>foo2</b></a>"), + ] + result, infotype = combine_docs(docs, transf) + + assert b"""<root><foo>foo1</foo><foo>foo2</foo></root>""" == result + assert infotype is None + + def test_combine_mixed(self): + docs = [ + JSONFeatureInfoDoc(b'{"results": [{"foo": 1}]}'), + TextFeatureInfoDoc(b"Plain Text"), + ] + result, infotype = combine_docs(docs, None) + + assert b"""{"results": [{"foo": 1}]}\nPlain Text""" == result + assert infotype == "text" diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_file_lock_load.py mapproxy-1.12.0/mapproxy/test/unit/test_file_lock_load.py --- mapproxy-1.11.0/mapproxy/test/unit/test_file_lock_load.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_file_lock_load.py 2019-08-30 07:34:08.000000000 +0000 @@ -1,42 +1,49 @@ -import tempfile -import os -import shutil -import time -import threading +# This file is part of the MapProxy project. +# Copyright (C) 2011 Omniscale <http://omniscale.de> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import multiprocessing +import threading +import time from mapproxy.util.lock import FileLock -from nose.tools import eq_ - -lock_dir = tempfile.mkdtemp() -lock_file = os.path.join(lock_dir, 'lock.lck') -count_file = os.path.join(lock_dir, 'count.txt') -open(count_file, 'w').write('0') -def lock(p=None): - l = FileLock(lock_file, timeout=60) +def lock(args): + lock_file, count_file = args + l = FileLock(lock_file.strpath, timeout=60) l.lock() - counter = int(open(count_file).read()) - open(count_file, 'w').write(str(counter+1)) + counter = int(count_file.read()) + count_file.write(str(counter+1)) time.sleep(0.001) l.unlock() -def test_file_lock_load(): + +def test_file_lock_load(tmpdir): + lock_file = tmpdir.join('lock.lck') + count_file = tmpdir.join('count.txt') + count_file.write('0') + def lock_x(): for x in range(5): time.sleep(0.01) - lock() + lock((lock_file, count_file)) threads = [threading.Thread(target=lock_x) for _ in range(20)] p = multiprocessing.Pool(5) [t.start() for t in threads] - p.map(lock, range(50)) + p.map(lock, [(lock_file, count_file) for _ in range(50)]) [t.join() for t in threads] - eq_(int(open(count_file).read()), 150) - - -def teardown(): - shutil.rmtree(lock_dir) - - + counter = int(count_file.read()) + assert counter == 150 diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_geom.py mapproxy-1.12.0/mapproxy/test/unit/test_geom.py --- mapproxy-1.11.0/mapproxy/test/unit/test_geom.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_geom.py 2019-08-30 07:34:08.000000000 +0000 @@ -19,6 +19,15 @@ import tempfile import shutil +import pytest +import shapely +import shapely.prepared +try: + # shapely >=1.6 + from shapely.errors import ReadingError +except ImportError: + from shapely.geos import ReadingError + from mapproxy.srs import SRS, bbox_equals from mapproxy.util.geom import ( load_polygons, @@ -39,21 +48,11 @@ ) from mapproxy.layer import MapExtent, DefaultMapExtent from mapproxy.test.helper import TempFile - -if not geom_support: - from nose.plugins.skip import SkipTest - raise SkipTest('requires Shapely') from mapproxy.util.coverage import BBOXCoverage -import shapely -import shapely.prepared -try: - # shapely >=1.6 - from shapely.errors import ReadingError -except ImportError: - from shapely.geos import ReadingError +pytestmark = pytest.mark.skipif(not geom_support, + reason="requires Shapely") -from nose.tools import eq_, raises VALID_POLYGON1 = b"""POLYGON ((953296.704552185838111 7265916.626927595585585, 944916.907243740395643 7266183.505430161952972, @@ -109,7 +108,7 @@ polygon = load_polygons(fname) bbox, polygon = build_multipolygon(polygon, simplify=True) assert polygon.is_valid - eq_(polygon.type, 'Polygon') + assert polygon.type == 'Polygon' def test_loading_multipolygon(self): with TempFile() as fname: @@ -120,16 +119,14 @@ polygon = load_polygons(fname) bbox, polygon = build_multipolygon(polygon, simplify=True) assert polygon.is_valid - eq_(polygon.type, 'MultiPolygon') + assert polygon.type == 'MultiPolygon' - @raises(ReadingError) def test_loading_broken(self): with TempFile() as fname: with open(fname, 'wb') as f: f.write(b"POLYGON((") - polygon = load_polygons(fname) - assert polygon.is_valid - bbox, polygon = build_multipolygon(polygon, simplify=True) + with pytest.raises(ReadingError): + load_polygons(fname) def test_loading_skip_non_polygon(self): with TempFile() as fname: @@ -139,7 +136,7 @@ polygon = load_polygons(fname) bbox, polygon = build_multipolygon(polygon, simplify=True) assert polygon.is_valid - eq_(polygon.type, 'Polygon') + assert polygon.type == 'Polygon' def test_loading_intersecting_polygons(self): # check that the self intersection is eliminated @@ -150,33 +147,22 @@ polygon = load_polygons(fname) bbox, polygon = build_multipolygon(polygon, simplify=True) assert polygon.is_valid - eq_(polygon.type, 'Polygon') + assert polygon.type == 'Polygon' assert polygon.equals(shapely.geometry.Polygon([(0, 0), (15, 0), (15, 10), (0, 10)])) class TestGeoJSONLoading(object): - def test_geojson(self): - yield (self.check_geojson, - '''{"type": "Polygon", "coordinates": [[[0, 0], [10, 0], [10, 10], [0, 0]]]}''', - shapely.geometry.Polygon([[0, 0], [10, 0], [10, 10], [0, 0]]), - ) - - yield (self.check_geojson, - '''{"type": "MultiPolygon", "coordinates": [[[[0, 0], [10, 0], [10, 10], [0, 0]]], [[[20, 0], [30, 0], [20, 10], [20, 0]]]]}''', - shapely.geometry.Polygon([[0, 0], [10, 0], [10, 10], [0, 0]]).union(shapely.geometry.Polygon([[20, 0], [30, 0], [20, 10], [20, 0]])), - ) - - yield (self.check_geojson, - '''{"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[0, 0], [10, 0], [10, 10], [0, 0]]]}}''', - shapely.geometry.Polygon([[0, 0], [10, 0], [10, 10], [0, 0]]), - ) - - yield (self.check_geojson, - '''{"type": "FeatureCollection", "features": [{"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[0, 0], [10, 0], [10, 10], [0, 0]]]}}]}''', - shapely.geometry.Polygon([[0, 0], [10, 0], [10, 10], [0, 0]]), - ) - - def check_geojson(self, geojson, geometry): + @pytest.mark.parametrize('geojson,geometry', [ + [ '''{"type": "Polygon", "coordinates": [[[0, 0], [10, 0], [10, 10], [0, 0]]]}''', + shapely.geometry.Polygon([[0, 0], [10, 0], [10, 10], [0, 0]]), ], + [ '''{"type": "MultiPolygon", "coordinates": [[[[0, 0], [10, 0], [10, 10], [0, 0]]], [[[20, 0], [30, 0], [20, 10], [20, 0]]]]}''', + shapely.geometry.Polygon([[0, 0], [10, 0], [10, 10], [0, 0]]).union(shapely.geometry.Polygon([[20, 0], [30, 0], [20, 10], [20, 0]])), ], + [ '''{"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[0, 0], [10, 0], [10, 10], [0, 0]]]}}''', + shapely.geometry.Polygon([[0, 0], [10, 0], [10, 10], [0, 0]]), ], + [ '''{"type": "FeatureCollection", "features": [{"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[0, 0], [10, 0], [10, 10], [0, 0]]]}}]}''', + shapely.geometry.Polygon([[0, 0], [10, 0], [10, 10], [0, 0]]), ], + ]) + def test_geojson(self, geojson, geometry): with TempFile() as fname: with open(fname, 'w') as f: f.write(geojson) @@ -211,15 +197,15 @@ assert mp3.symmetric_difference(mp1).area < 0.00001 - @raises(ValueError) def test_invalid_transf(self): p = shapely.geometry.Point((0, 0)) - transform_geometry(SRS(4326), SRS(900913), p) + with pytest.raises(ValueError): + transform_geometry(SRS(4326), SRS(900913), p) class TestBBOXPolygon(object): def test_bbox_polygon(self): p = bbox_polygon([5, 53, 6, 54]) - eq_(p.type, 'Polygon') + assert p.type == 'Polygon' class TestGeomCoverage(object): @@ -233,7 +219,7 @@ assert bbox_equals(self.coverage.bbox, [-10, 10, 80, 80], 0.0001) def test_geom(self): - eq_(self.coverage.geom.type, 'Polygon') + assert self.coverage.geom.type == 'Polygon' def test_contains(self): assert self.coverage.contains((15, 15, 20, 20), SRS(4326)) @@ -274,7 +260,7 @@ assert bbox_equals(self.coverage.bbox, [-10, 10, 80, 80], 0.0001) def test_geom(self): - eq_(self.coverage.geom, None) + assert self.coverage.geom == None def test_contains(self): assert self.coverage.contains((15, 15, 20, 20), SRS(4326)) @@ -293,17 +279,17 @@ assert self.coverage.intersects((0, 0, 1500000, 1500000), SRS(900913)) def test_intersection(self): - eq_(self.coverage.intersection((15, 15, 20, 20), SRS(4326)), + assert (self.coverage.intersection((15, 15, 20, 20), SRS(4326)) == BBOXCoverage((15, 15, 20, 20), SRS(4326))) - eq_(self.coverage.intersection((15, 15, 80, 20), SRS(4326)), + assert (self.coverage.intersection((15, 15, 80, 20), SRS(4326)) == BBOXCoverage((15, 15, 80, 20), SRS(4326))) - eq_(self.coverage.intersection((9, 10, 20, 20), SRS(4326)), + assert (self.coverage.intersection((9, 10, 20, 20), SRS(4326)) == BBOXCoverage((9, 10, 20, 20), SRS(4326))) - eq_(self.coverage.intersection((-30, 10, -8, 70), SRS(4326)), + assert (self.coverage.intersection((-30, 10, -8, 70), SRS(4326)) == BBOXCoverage((-10, 10, -8, 70), SRS(4326))) - eq_(self.coverage.intersection((-30, 10, -11, 70), SRS(4326)), + assert (self.coverage.intersection((-30, 10, -11, 70), SRS(4326)) == None) - eq_(self.coverage.intersection((0, 0, 1000, 1000), SRS(900913)), + assert (self.coverage.intersection((0, 0, 1000, 1000), SRS(900913)) == None) assert bbox_equals( self.coverage.intersection((0, 0, 1500000, 1500000), SRS(900913)).bbox, @@ -467,7 +453,7 @@ def test_shp(self): polygon_file = os.path.join(os.path.dirname(__file__), 'polygons', 'polygons.shp') geoms = load_datasource(polygon_file) - eq_(len(geoms), 3) + assert len(geoms) == 3 def test_wkt(self): with TempFile() as fname: @@ -477,7 +463,7 @@ f.write(VALID_POLYGON1) geoms = load_datasource(fname) - eq_(len(geoms), 2) + assert len(geoms) == 2 def test_geojson(self): with TempFile() as fname: @@ -489,7 +475,7 @@ ]}''') geoms = load_datasource(fname) - eq_(len(geoms), 4) + assert len(geoms) == 4 def test_expire_tiles_dir(self): dirname = tempfile.mkdtemp() @@ -501,7 +487,7 @@ f.write(b"4/4/3\n") geoms = load_expire_tiles(dirname) - eq_(len(geoms), 3) + assert len(geoms) == 3 finally: shutil.rmtree(dirname) @@ -514,4 +500,4 @@ f.write(b"4/2/1\n") # rest of file is ignored geoms = load_expire_tiles(fname) - eq_(len(geoms), 2) + assert len(geoms) == 2 diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_grid.py mapproxy-1.12.0/mapproxy/test/unit/test_grid.py --- mapproxy-1.11.0/mapproxy/test/unit/test_grid.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_grid.py 2019-08-30 07:34:08.000000000 +0000 @@ -15,7 +15,6 @@ from __future__ import print_function, division -from nose.tools import eq_, assert_almost_equal, raises from mapproxy.grid import ( MetaGrid, @@ -32,49 +31,52 @@ ) from mapproxy.srs import SRS, TransformationError +import pytest + + class TestResolution(object): def test_min_res(self): conf = dict(min_res=1000) res = resolutions(**conf) - eq_(res[:5], [1000, 500.0, 250.0, 125.0, 62.5]) - eq_(len(res), 20) + assert res[:5] == [1000, 500.0, 250.0, 125.0, 62.5] + assert len(res) == 20 def test_min_res_max_res(self): conf = dict(min_res=1000, max_res=80) res = resolutions(**conf) - eq_(res, [1000, 500.0, 250.0, 125.0]) + assert res == [1000, 500.0, 250.0, 125.0] def test_min_res_levels(self): conf = dict(min_res=1600, num_levels=5) res = resolutions(**conf) - eq_(res, [1600, 800.0, 400.0, 200.0, 100.0]) + assert res == [1600, 800.0, 400.0, 200.0, 100.0] def test_min_res_levels_res_factor(self): conf = dict(min_res=1600, num_levels=4, res_factor=4.0) res = resolutions(**conf) - eq_(res, [1600, 400.0, 100.0, 25.0]) + assert res == [1600, 400.0, 100.0, 25.0] def test_min_res_levels_sqrt2(self): conf = dict(min_res=1600, num_levels=5, res_factor='sqrt2') res = resolutions(**conf) - eq_(list(map(round, res)), [1600.0, 1131.0, 800.0, 566.0, 400.0]) + assert res == pytest.approx([1600.0, 1131.0, 800.0, 566.0, 400.0], 0.1) def test_min_res_max_res_levels(self): conf = dict(min_res=1600, max_res=10, num_levels=10) res = resolutions(**conf) - eq_(len(res), 10) + assert len(res) == 10 # will calculate log10 based factor of 1.75752... - assert_almost_equal(res[0], 1600) - assert_almost_equal(res[1], 1600/1.75752, 2) - assert_almost_equal(res[8], 1600/1.75752**8, 2) - assert_almost_equal(res[9], 10) + assert res[0] == pytest.approx(1600) + assert res[1] == pytest.approx(1600/1.75752, 0.01) + assert res[8] == pytest.approx(1600/1.75752**8, 0.01) + assert res[9] == pytest.approx(10) def test_bbox_levels(self): conf = dict(bbox=[0,40,15,50], num_levels=10, tile_size=(256, 256)) res = resolutions(**conf) - eq_(len(res), 10) - assert_almost_equal(res[0], 15/256) - assert_almost_equal(res[1], 15/512) + assert len(res) == 10 + assert res[0] == pytest.approx(15/256) + assert res[1] == pytest.approx(15/512) class TestAlignedGrid(object): @@ -83,35 +85,35 @@ bbox = (10.0, -20.0, 40.0, 10.0) sub = tile_grid(align_with=base, bbox=bbox) - eq_(sub.bbox, bbox) - eq_(sub.resolution(0), 180/256/8) + assert sub.bbox == bbox + assert sub.resolution(0) == 180/256/8 abbox, grid_size, tiles = sub.get_affected_level_tiles(bbox, 0) - eq_(abbox, (10.0, -20.0, 55.0, 25.0)) - eq_(grid_size, (2, 2)) - eq_(list(tiles), [(0, 1, 0), (1, 1, 0), (0, 0, 0), (1, 0, 0)]) + assert abbox == (10.0, -20.0, 55.0, 25.0) + assert grid_size == (2, 2) + assert list(tiles) == [(0, 1, 0), (1, 1, 0), (0, 0, 0), (1, 0, 0)] def test_epsg_4326_bbox_from_sqrt2(self): base = tile_grid(srs='epsg:4326', res_factor='sqrt2') bbox = (10.0, -20.0, 40.0, 10.0) sub = tile_grid(align_with=base, bbox=bbox, res_factor=2.0) - eq_(sub.bbox, bbox) - eq_(sub.resolution(0), base.resolution(8)) - eq_(sub.resolution(1), base.resolution(10)) - eq_(sub.resolution(2), base.resolution(12)) + assert sub.bbox == bbox + assert sub.resolution(0) == base.resolution(8) + assert sub.resolution(1) == base.resolution(10) + assert sub.resolution(2) == base.resolution(12) def test_epsg_4326_bbox_to_sqrt2(self): base = tile_grid(srs='epsg:4326', res_factor=2.0) bbox = (10.0, -20.0, 40.0, 10.0) sub = tile_grid(align_with=base, bbox=bbox, res_factor='sqrt2') - eq_(sub.bbox, bbox) - eq_(sub.resolution(0), base.resolution(4)) - eq_(sub.resolution(2), base.resolution(5)) - eq_(sub.resolution(4), base.resolution(6)) + assert sub.bbox == bbox + assert sub.resolution(0) == base.resolution(4) + assert sub.resolution(2) == base.resolution(5) + assert sub.resolution(4) == base.resolution(6) assert sub.resolution(0) > sub.resolution(1) > sub.resolution(3) - eq_(sub.resolution(3)/2, sub.resolution(5)) + assert sub.resolution(3)/2 == sub.resolution(5) def test_metagrid_tiles(): @@ -139,54 +141,54 @@ self.mgrid = MetaGrid(grid=tile_grid('EPSG:4326'), meta_size=(2, 2), meta_buffer=10) def test_meta_bbox_level_0(self): - eq_(self.mgrid._meta_bbox((0, 0, 0)), ((-180, -90, 180, 90), (0, 0, 0, -128))) - eq_(self.mgrid._meta_bbox((0, 0, 0), limit_to_bbox=False), + assert self.mgrid._meta_bbox((0, 0, 0)) == ((-180, -90, 180, 90), (0, 0, 0, -128)) + assert (self.mgrid._meta_bbox((0, 0, 0), limit_to_bbox=False) == ((-194.0625, -104.0625, 194.0625, 284.0625), (10, 10, 10, 10))) - eq_(self.mgrid.meta_tile((0, 0, 0)).size, (256, 128)) + assert self.mgrid.meta_tile((0, 0, 0)).size == (256, 128) def test_tiles_level_0(self): meta_tile = self.mgrid.meta_tile((0, 0, 0)) - eq_(meta_tile.size, (256, 128)) - eq_(meta_tile.grid_size, (1, 1)) - eq_(meta_tile.tile_patterns, [((0, 0, 0), (0, -128))]) + assert meta_tile.size == (256, 128) + assert meta_tile.grid_size == (1, 1) + assert meta_tile.tile_patterns == [((0, 0, 0), (0, -128))] def test_meta_bbox_level_1(self): - eq_(self.mgrid._meta_bbox((0, 0, 1)), ((-180, -90, 180, 90), (0, 0, 0, 0))) - eq_(self.mgrid._meta_bbox((0, 0, 1), limit_to_bbox=False), + assert (self.mgrid._meta_bbox((0, 0, 1)) == ((-180, -90, 180, 90), (0, 0, 0, 0))) + assert (self.mgrid._meta_bbox((0, 0, 1), limit_to_bbox=False) == ((-187.03125, -97.03125, 187.03125, 97.03125), (10, 10, 10, 10))) - eq_(self.mgrid.meta_tile((0, 0, 1)).size, (512, 256)) + assert (self.mgrid.meta_tile((0, 0, 1)).size == (512, 256)) def test_tiles_level_1(self): - eq_(list(self.mgrid.meta_tile((0, 0, 1)).tile_patterns), + assert (list(self.mgrid.meta_tile((0, 0, 1)).tile_patterns) == [ ((0, 0, 1), (0, 0)), ((1, 0, 1), (256, 0)) ]) def test_tile_list_level_1(self): - eq_(list(self.mgrid.tile_list((0, 0, 1))), + assert (list(self.mgrid.tile_list((0, 0, 1))) == [(0, 0, 1), (1, 0, 1)]) def test_meta_bbox_level_2(self): - eq_(self.mgrid._meta_bbox((0, 0, 2)), ((-180, -90, 3.515625, 90), (0, 0, 10, 0))) - eq_(self.mgrid._meta_bbox((0, 0, 2), limit_to_bbox=False), + assert (self.mgrid._meta_bbox((0, 0, 2)) == ((-180, -90, 3.515625, 90), (0, 0, 10, 0))) + assert (self.mgrid._meta_bbox((0, 0, 2), limit_to_bbox=False) == ((-183.515625, -93.515625, 3.515625, 93.515625), (10, 10, 10, 10))) - eq_(self.mgrid.meta_tile((0, 0, 2)).size, (522, 512)) + assert (self.mgrid.meta_tile((0, 0, 2)).size == (522, 512)) - eq_(self.mgrid._meta_bbox((2, 0, 2)), ((-3.515625, -90, 180, 90), (10, 0, 0, 0))) + assert (self.mgrid._meta_bbox((2, 0, 2)) == ((-3.515625, -90, 180, 90), (10, 0, 0, 0))) meta_tile = self.mgrid.meta_tile((2, 0, 2)) - eq_(meta_tile.size, (522, 512)) - eq_(meta_tile.grid_size, (2, 2)) + assert meta_tile.size == (522, 512) + assert meta_tile.grid_size == (2, 2) def test_tiles_level_2(self): - eq_(list(self.mgrid.meta_tile((0, 0, 2)).tile_patterns), + assert (list(self.mgrid.meta_tile((0, 0, 2)).tile_patterns) == [ ((0, 1, 2), (0, 0)), ((1, 1, 2), (256, 0)), ((0, 0, 2), (0, 256)), ((1, 0, 2), (256, 256)), ]) - eq_(list(self.mgrid.meta_tile((2, 0, 2)).tile_patterns), + assert (list(self.mgrid.meta_tile((2, 0, 2)).tile_patterns) == [ ((2, 1, 2), (10, 0)), ((3, 1, 2), (266, 0)), @@ -195,20 +197,20 @@ ]) def test_tile_list_level_2(self): - eq_(list(self.mgrid.tile_list((0, 0, 2))), + assert (list(self.mgrid.tile_list((0, 0, 2))) == [(0, 1, 2), (1, 1, 2), (0, 0, 2), (1, 0, 2)]) - eq_(list(self.mgrid.tile_list((1, 1, 2))), + assert (list(self.mgrid.tile_list((1, 1, 2))) == [(0, 1, 2), (1, 1, 2), (0, 0, 2), (1, 0, 2)]) def test_tiles_level_3(self): - eq_(list(self.mgrid.meta_tile((2, 0, 3)).tile_patterns), + assert (list(self.mgrid.meta_tile((2, 0, 3)).tile_patterns) == [ ((2, 1, 3), (10, 10)), ((3, 1, 3), (266, 10)), ((2, 0, 3), (10, 266)), ((3, 0, 3), (266, 266)), ]) - eq_(list(self.mgrid.meta_tile((2, 2, 3)).tile_patterns), + assert (list(self.mgrid.meta_tile((2, 2, 3)).tile_patterns) == [ ((2, 3, 3), (10, 0)), ((3, 3, 3), (266, 0)), @@ -223,40 +225,40 @@ def test_tiles_level_0(self): meta_tile = self.mgrid.meta_tile((0, 0, 0)) - eq_(meta_tile.bbox, (-180, -90, 180, 90)) - eq_(meta_tile.size, (256, 128)) - eq_(meta_tile.grid_size, (1, 1)) - eq_(meta_tile.tile_patterns, [((0, 0, 0), (0, 0))]) + assert meta_tile.bbox == pytest.approx((-180, -90, 180, 90)) + assert meta_tile.size == (256, 128) + assert meta_tile.grid_size == (1, 1) + assert meta_tile.tile_patterns == [((0, 0, 0), (0, 0))] def test_tiles_level_1(self): meta_tile = self.mgrid.meta_tile((0, 0, 1)) - eq_(meta_tile.bbox, (-180, -90, 180, 90)) - eq_(meta_tile.size, (512, 256)) - eq_(meta_tile.grid_size, (2, 1)) - eq_(list(meta_tile.tile_patterns), + assert meta_tile.bbox == pytest.approx((-180, -90, 180, 90)) + assert meta_tile.size == (512, 256) + assert meta_tile.grid_size == (2, 1) + assert (list(meta_tile.tile_patterns) == [ ((0, 0, 1), (0, 0)), ((1, 0, 1), (256, 0)) ]) def test_tile_list_level_1(self): - eq_(list(self.mgrid.tile_list((0, 0, 1))), + assert (list(self.mgrid.tile_list((0, 0, 1))) == [(0, 0, 1), (1, 0, 1)]) def test_tiles_level_2(self): meta_tile = self.mgrid.meta_tile((0, 0, 2)) - eq_(meta_tile.bbox, (-180, -90, 3.515625, 90)) - eq_(meta_tile.size, (522, 512)) - eq_(meta_tile.grid_size, (2, 2)) - eq_(meta_tile.tile_patterns, + assert meta_tile.bbox == pytest.approx((-180, -90, 3.515625, 90)) + assert meta_tile.size == (522, 512) + assert meta_tile.grid_size == (2, 2) + assert (meta_tile.tile_patterns == [ ((0, 0, 2), (0, 0)), ((1, 0, 2), (256, 0)), ((0, 1, 2), (0, 256)), ((1, 1, 2), (256, 256)), ]) - eq_(list(self.mgrid.meta_tile((2, 0, 2)).tile_patterns), + assert (list(self.mgrid.meta_tile((2, 0, 2)).tile_patterns) == [ ((2, 0, 2), (10, 0)), ((3, 0, 2), (266, 0)), @@ -265,24 +267,24 @@ ]) def test_tile_list_level_2(self): - eq_(list(self.mgrid.tile_list((0, 0, 2))), + assert (list(self.mgrid.tile_list((0, 0, 2))) == [(0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2)]) - eq_(list(self.mgrid.tile_list((1, 1, 2))), + assert (list(self.mgrid.tile_list((1, 1, 2))) == [(0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2)]) def test_tiles_level_3(self): meta_tile = self.mgrid.meta_tile((2, 0, 3)) - eq_(meta_tile.bbox, (-91.7578125, -1.7578125, 1.7578125, 90)) - eq_(meta_tile.size, (532, 522)) - eq_(meta_tile.grid_size, (2, 2)) - eq_(list(self.mgrid.meta_tile((2, 0, 3)).tile_patterns), + assert meta_tile.bbox == pytest.approx((-91.7578125, -1.7578125, 1.7578125, 90)) + assert meta_tile.size == (532, 522) + assert meta_tile.grid_size == (2, 2) + assert (list(self.mgrid.meta_tile((2, 0, 3)).tile_patterns) == [ ((2, 0, 3), (10, 0)), ((3, 0, 3), (266, 0)), ((2, 1, 3), (10, 256)), ((3, 1, 3), (266, 256)), ]) - eq_(list(self.mgrid.meta_tile((2, 2, 3)).tile_patterns), + assert (list(self.mgrid.meta_tile((2, 2, 3)).tile_patterns) == [ ((2, 2, 3), (10, 10)), ((3, 2, 3), (266, 10)), @@ -296,23 +298,23 @@ self.mgrid = MetaGrid(grid=tile_grid('EPSG:4326'), meta_size=(2, 2), meta_buffer=10) def test_meta_tile(self): meta_tile = self.mgrid.meta_tile((2, 0, 2)) - eq_(meta_tile.size, (522, 512)) + assert meta_tile.size == (522, 512) def test_metatile_bbox(self): mgrid = MetaGrid(grid=TileGrid(), meta_size=(2, 2)) meta_tile = mgrid.meta_tile((0, 0, 2)) - assert meta_tile.bbox == (-20037508.342789244, -20037508.342789244, 0.0, 0.0) + assert meta_tile.bbox == pytest.approx((-20037508.342789244, -20037508.342789244, 0.0, 0.0)) meta_tile = mgrid.meta_tile((1, 1, 2)) - assert meta_tile.bbox == (-20037508.342789244, -20037508.342789244, 0.0, 0.0) + assert meta_tile.bbox == pytest.approx((-20037508.342789244, -20037508.342789244, 0.0, 0.0)) meta_tile = mgrid.meta_tile((4, 5, 3)) - assert_almost_equal_bbox(meta_tile.bbox, (0.0, 0.0, 10018754.171394622, 10018754.171394622)) + assert meta_tile.bbox == pytest.approx((0.0, 0.0, 10018754.171394622, 10018754.171394622)) def test_metatile_non_default_meta_size(self): mgrid = MetaGrid(grid=TileGrid(), meta_size=(4, 2)) meta_tile = mgrid.meta_tile((4, 5, 3)) - assert_almost_equal_bbox(meta_tile.bbox, (0.0, 0.0, 20037508.342789244, 10018754.171394622)) - eq_(meta_tile.size, (1024, 512)) - eq_(meta_tile.grid_size, (4, 2)) + assert meta_tile.bbox == pytest.approx((0.0, 0.0, 20037508.342789244, 10018754.171394622)) + assert meta_tile.size == (1024, 512) + assert meta_tile.grid_size == (4, 2) class TestMetaTileSQRT2(object): def setup(self): @@ -320,36 +322,36 @@ self.mgrid = MetaGrid(grid=self.grid, meta_size=(4, 4), meta_buffer=10) def test_meta_tile(self): meta_tile = self.mgrid.meta_tile((0, 0, 8)) - eq_(meta_tile.size, (1034, 1034)) + assert meta_tile.size == (1034, 1034) def test_metatile_bbox(self): meta_tile = self.mgrid.meta_tile((0, 0, 2)) - eq_(meta_tile.bbox, (-180, -90, 180, 90)) - eq_(meta_tile.size, (512, 256)) - eq_(meta_tile.grid_size, (2, 1)) - eq_(meta_tile.tile_patterns, [((0, 0, 2), (0, 0)), ((1, 0, 2), (256, 0))]) + assert meta_tile.bbox == pytest.approx((-180, -90, 180, 90)) + assert meta_tile.size == (512, 256) + assert meta_tile.grid_size == (2, 1) + assert meta_tile.tile_patterns == [((0, 0, 2), (0, 0)), ((1, 0, 2), (256, 0))] meta_tile = self.mgrid.meta_tile((1, 0, 2)) - eq_(meta_tile.bbox, (-180.0, -90, 180.0, 90.0)) - eq_(meta_tile.size, (512, 256)) - eq_(meta_tile.grid_size, (2, 1)) + assert meta_tile.bbox == pytest.approx((-180.0, -90, 180.0, 90.0)) + assert meta_tile.size == (512, 256) + assert meta_tile.grid_size == (2, 1) meta_tile = self.mgrid.meta_tile((0, 0, 3)) - eq_(meta_tile.bbox, (-180.0, -90, 180.0, 90.0)) - eq_(meta_tile.size, (724, 362)) - eq_(meta_tile.tile_patterns, [((0, 1, 3), (0, -149)), ((1, 1, 3), (256, -149)), + assert meta_tile.bbox == pytest.approx((-180.0, -90, 180.0, 90.0)) + assert meta_tile.size == (724, 362) + assert meta_tile.tile_patterns == [((0, 1, 3), (0, -149)), ((1, 1, 3), (256, -149)), ((2, 1, 3), (512, -149)), ((0, 0, 3), (0, 107)), ((1, 0, 3), (256, 107)), - ((2, 0, 3), (512, 107))]) + ((2, 0, 3), (512, 107))] def test_metatile_non_default_meta_size(self): mgrid = MetaGrid(grid=self.grid, meta_size=(4, 2), meta_buffer=0) meta_tile = mgrid.meta_tile((4, 3, 6)) - eq_(meta_tile.bbox, (0.0, 0.0, 180.0, 90.0)) - eq_(meta_tile.size, (1024, 512)) - eq_(meta_tile.grid_size, (4, 2)) - eq_(meta_tile.tile_patterns, [((4, 3, 6), (0, 0)), ((5, 3, 6), (256, 0)), + assert meta_tile.bbox == pytest.approx((0.0, 0.0, 180.0, 90.0)) + assert meta_tile.size == (1024, 512) + assert meta_tile.grid_size == (4, 2) + assert meta_tile.tile_patterns == [((4, 3, 6), (0, 0)), ((5, 3, 6), (256, 0)), ((6, 3, 6), (512, 0)), ((7, 3, 6), (768, 0)), ((4, 2, 6), (0, 256)), - ((5, 2, 6), (256, 256)), ((6, 2, 6), (512, 256)), ((7, 2, 6), (768, 256))]) + ((5, 2, 6), (256, 256)), ((6, 2, 6), (512, 256)), ((7, 2, 6), (768, 256))] @@ -360,14 +362,14 @@ def test_minimal_tiles(self): sgrid = self.mgrid.minimal_meta_tile([(0, 0, 2), (1, 0, 2)]) - eq_(sgrid.grid_size, (2, 1)) - eq_(list(sgrid.tile_patterns), + assert sgrid.grid_size == (2, 1) + assert (list(sgrid.tile_patterns) == [ ((0, 0, 2), (0, 10)), ((1, 0, 2), (256, 10)), ] ) - eq_(sgrid.bbox, (-180.0, -90.0, 3.515625, 3.515625)) + assert sgrid.bbox == (-180.0, -90.0, 3.515625, 3.515625) def test_minimal_tiles_fragmented(self): sgrid = self.mgrid.minimal_meta_tile( @@ -377,15 +379,15 @@ (2, 1, 3), ]) - eq_(sgrid.grid_size, (2, 3)) - eq_(list(sgrid.tile_patterns), + assert sgrid.grid_size == (2, 3) + assert (list(sgrid.tile_patterns) == [ ((1, 3, 3), (10, 0)), ((2, 3, 3), (266, 0)), ((1, 2, 3), (10, 256)), ((2, 2, 3), (266, 256)), ((1, 1, 3), (10, 512)), ((2, 1, 3), (266, 512)), ] ) - eq_(sgrid.bbox, (-136.7578125, -46.7578125, -43.2421875, 90.0)) + assert sgrid.bbox == (-136.7578125, -46.7578125, -43.2421875, 90.0) def test_minimal_tiles_fragmented_ul(self): self.mgrid = MetaGrid(grid=tile_grid('EPSG:4326', origin='ul'), @@ -397,19 +399,19 @@ (2, 2, 3), ]) - eq_(sgrid.grid_size, (2, 3)) - eq_(list(sgrid.tile_patterns), + assert sgrid.grid_size == (2, 3) + assert (list(sgrid.tile_patterns) == [ ((1, 0, 3), (10, 0)), ((2, 0, 3), (266, 0)), ((1, 1, 3), (10, 256)), ((2, 1, 3), (266, 256)), ((1, 2, 3), (10, 512)), ((2, 2, 3), (266, 512)), ] ) - eq_(sgrid.bbox, (-136.7578125, -46.7578125, -43.2421875, 90.0)) + assert sgrid.bbox == (-136.7578125, -46.7578125, -43.2421875, 90.0) class TestMetaGridLevelMetaTiles(object): - def __init__(self): + def setup(self): self.meta_grid = MetaGrid(TileGrid(), meta_size=(2, 2)) def test_full_grid_0(self): @@ -419,8 +421,8 @@ meta_tiles = list(meta_tiles) assert_almost_equal_bbox(bbox, abbox) - eq_(len(meta_tiles), 1) - eq_(meta_tiles[0], (0, 0, 0)) + assert len(meta_tiles) == 1 + assert meta_tiles[0] == (0, 0, 0) def test_full_grid_2(self): bbox = (-20037508.34, -20037508.34, 20037508.34, 20037508.34) @@ -429,15 +431,15 @@ meta_tiles = list(meta_tiles) assert_almost_equal_bbox(bbox, abbox) - eq_(tile_grid, (2, 2)) - eq_(len(meta_tiles), 4) - eq_(meta_tiles[0], (0, 2, 2)) - eq_(meta_tiles[1], (2, 2, 2)) - eq_(meta_tiles[2], (0, 0, 2)) - eq_(meta_tiles[3], (2, 0, 2)) + assert tile_grid == (2, 2) + assert len(meta_tiles) == 4 + assert meta_tiles[0] == (0, 2, 2) + assert meta_tiles[1] == (2, 2, 2) + assert meta_tiles[2] == (0, 0, 2) + assert meta_tiles[3] == (2, 0, 2) class TestMetaGridLevelMetaTilesGeodetic(object): - def __init__(self): + def setup(self): self.meta_grid = MetaGrid(TileGrid(is_geodetic=True), meta_size=(2, 2)) def test_full_grid_2(self): @@ -447,10 +449,10 @@ meta_tiles = list(meta_tiles) assert_almost_equal_bbox(bbox, abbox) - eq_(tile_grid, (2, 1)) - eq_(len(meta_tiles), 2) - eq_(meta_tiles[0], (0, 0, 2)) - eq_(meta_tiles[1], (2, 0, 2)) + assert tile_grid == (2, 1) + assert len(meta_tiles) == 2 + assert meta_tiles[0] == (0, 0, 2) + assert meta_tiles[1] == (2, 0, 2) def test_partial_grid_3(self): bbox = (0.0, 5.0, 45, 40) @@ -459,9 +461,9 @@ meta_tiles = list(meta_tiles) assert_almost_equal_bbox((0.0, 0.0, 90.0, 90.0), abbox) - eq_(tile_grid, (1, 1)) - eq_(len(meta_tiles), 1) - eq_(meta_tiles[0], (4, 2, 3)) + assert tile_grid == (1, 1) + assert len(meta_tiles) == 1 + assert meta_tiles[0] == (4, 2, 3) def assert_grid_size(grid, level, grid_size): @@ -480,23 +482,23 @@ class TestTileGridResolutions(object): def test_explicit_grid(self): grid = TileGrid(res=[0.1, 0.05, 0.01]) - eq_(grid.resolution(0), 0.1) - eq_(grid.resolution(1), 0.05) - eq_(grid.resolution(2), 0.01) + assert grid.resolution(0) == 0.1 + assert grid.resolution(1) == 0.05 + assert grid.resolution(2) == 0.01 - eq_(grid.closest_level(0.00001), 2) + assert grid.closest_level(0.00001) == 2 def test_factor_grid(self): grid = TileGrid(is_geodetic=True, res=1/0.75, tile_size=(360, 180)) - eq_(grid.resolution(0), 1.0) - eq_(grid.resolution(1), 0.75) - eq_(grid.resolution(2), 0.75*0.75) + assert grid.resolution(0) == 1.0 + assert grid.resolution(1) == 0.75 + assert grid.resolution(2) == 0.75*0.75 def test_sqrt_grid(self): grid = TileGrid(is_geodetic=True, res='sqrt2', tile_size=(360, 180)) - eq_(grid.resolution(0), 1.0) - assert_almost_equal(grid.resolution(2), 0.5) - assert_almost_equal(grid.resolution(4), 0.25) + assert grid.resolution(0) == 1.0 + assert grid.resolution(2) == pytest.approx(0.5) + assert grid.resolution(4) == pytest.approx(0.25) class TestWGS84TileGrid(object): @@ -504,83 +506,83 @@ self.grid = TileGrid(is_geodetic=True) def test_resolution(self): - assert_almost_equal(self.grid.resolution(0), 1.40625) - assert_almost_equal(self.grid.resolution(1), 1.40625/2) + assert self.grid.resolution(0) == pytest.approx(1.40625) + assert self.grid.resolution(1) == pytest.approx(1.40625/2) def test_bbox(self): - eq_(self.grid.bbox, (-180.0, -90.0, 180.0, 90.0)) + assert self.grid.bbox == (-180.0, -90.0, 180.0, 90.0) def test_grid_size(self): - eq_(self.grid.grid_sizes[0], (1, 1)) - eq_(self.grid.grid_sizes[1], (2, 1)) - eq_(self.grid.grid_sizes[2], (4, 2)) + assert self.grid.grid_sizes[0] == (1, 1) + assert self.grid.grid_sizes[1] == (2, 1) + assert self.grid.grid_sizes[2] == (4, 2) def test_affected_tiles(self): bbox, grid, tiles = self.grid.get_affected_tiles((-180,-90,180,90), (512,256)) - eq_(bbox, (-180.0, -90.0, 180.0, 90.0)) - eq_(grid, (2, 1)) - eq_(list(tiles), [(0, 0, 1), (1, 0, 1)]) + assert bbox == (-180.0, -90.0, 180.0, 90.0) + assert grid == (2, 1) + assert list(tiles) == [(0, 0, 1), (1, 0, 1)] def test_affected_level_tiles(self): bbox, grid, tiles = self.grid.get_affected_level_tiles((-180,-90,180,90), 1) - eq_(grid, (2, 1)) - eq_(bbox, (-180.0, -90.0, 180.0, 90.0)) - eq_(list(tiles), [(0, 0, 1), (1, 0, 1)]) + assert grid == (2, 1) + assert bbox == (-180.0, -90.0, 180.0, 90.0) + assert list(tiles) == [(0, 0, 1), (1, 0, 1)] bbox, grid, tiles = self.grid.get_affected_level_tiles((0,0,180,90), 2) - eq_(grid, (2, 1)) - eq_(bbox, (0.0, 0.0, 180.0, 90.0)) - eq_(list(tiles), [(2, 1, 2), (3, 1, 2)]) + assert grid == (2, 1) + assert bbox == (0.0, 0.0, 180.0, 90.0) + assert list(tiles) == [(2, 1, 2), (3, 1, 2)] class TestWGS83TileGridUL(object): def setup(self): self.grid = TileGrid(4326, bbox=(-180, -90, 180, 90), origin='ul') def test_resolution(self): - assert_almost_equal(self.grid.resolution(0), 1.40625) - assert_almost_equal(self.grid.resolution(1), 1.40625/2) + assert self.grid.resolution(0) == pytest.approx(1.40625) + assert self.grid.resolution(1) == pytest.approx(1.40625/2) def test_bbox(self): - eq_(self.grid.bbox, (-180.0, -90.0, 180.0, 90.0)) + assert self.grid.bbox == (-180.0, -90.0, 180.0, 90.0) def test_tile_bbox(self): - eq_(self.grid.tile_bbox((0, 0, 0)), (-180.0, -270.0, 180.0, 90.0)) - eq_(self.grid.tile_bbox((0, 0, 0), limit=True), (-180.0, -90.0, 180.0, 90.0)) - eq_(self.grid.tile_bbox((0, 0, 1)), (-180.0, -90.0, 0.0, 90.0)) + assert (self.grid.tile_bbox((0, 0, 0)) == (-180.0, -270.0, 180.0, 90.0)) + assert (self.grid.tile_bbox((0, 0, 0), limit=True) == (-180.0, -90.0, 180.0, 90.0)) + assert (self.grid.tile_bbox((0, 0, 1)) == (-180.0, -90.0, 0.0, 90.0)) def test_tile(self): - eq_(self.grid.tile(-170, -80, 0), (0, 0, 0)) - eq_(self.grid.tile(-170, -80, 1), (0, 0, 1)) - eq_(self.grid.tile(-170, -80, 2), (0, 1, 2)) + assert (self.grid.tile(-170, -80, 0) == (0, 0, 0)) + assert (self.grid.tile(-170, -80, 1) == (0, 0, 1)) + assert (self.grid.tile(-170, -80, 2) == (0, 1, 2)) def test_grid_size(self): - eq_(self.grid.grid_sizes[0], (1, 1)) - eq_(self.grid.grid_sizes[1], (2, 1)) - eq_(self.grid.grid_sizes[2], (4, 2)) + assert self.grid.grid_sizes[0] == (1, 1) + assert self.grid.grid_sizes[1] == (2, 1) + assert self.grid.grid_sizes[2] == (4, 2) def test_affected_tiles(self): bbox, grid, tiles = self.grid.get_affected_tiles((-180,-90,180,90), (512,256)) - eq_(bbox, (-180.0, -90.0, 180.0, 90.0)) - eq_(grid, (2, 1)) - eq_(list(tiles), [(0, 0, 1), (1, 0, 1)]) + assert bbox == (-180.0, -90.0, 180.0, 90.0) + assert grid == (2, 1) + assert list(tiles) == [(0, 0, 1), (1, 0, 1)] bbox, grid, tiles = self.grid.get_affected_tiles((-180,-90,0,90), (512, 512)) - eq_(bbox, (-180.0, -90.0, 0.0, 90.0)) - eq_(grid, (2, 2)) - eq_(list(tiles), [(0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2)]) + assert bbox == (-180.0, -90.0, 0.0, 90.0) + assert grid == (2, 2) + assert list(tiles) == [(0, 0, 2), (1, 0, 2), (0, 1, 2), (1, 1, 2)] def test_affected_level_tiles(self): bbox, grid, tiles = self.grid.get_affected_level_tiles((-180,-90,180,90), 1) - eq_(grid, (2, 1)) - eq_(bbox, (-180.0, -90.0, 180.0, 90.0)) - eq_(list(tiles), [(0, 0, 1), (1, 0, 1)]) + assert grid == (2, 1) + assert bbox == (-180.0, -90.0, 180.0, 90.0) + assert list(tiles) == [(0, 0, 1), (1, 0, 1)] bbox, grid, tiles = self.grid.get_affected_level_tiles((0,0,180,90), 2) - eq_(grid, (2, 1)) - eq_(list(tiles), [(2, 0, 2), (3, 0, 2)]) - eq_(bbox, (0.0, 0.0, 180.0, 90.0)) + assert grid == (2, 1) + assert list(tiles) == [(2, 0, 2), (3, 0, 2)] + assert bbox == (0.0, 0.0, 180.0, 90.0) bbox, grid, tiles = self.grid.get_affected_level_tiles((0,-90,180,90), 2) - eq_(grid, (2, 2)) - eq_(list(tiles), [(2, 0, 2), (3, 0, 2), (2, 1, 2), (3, 1, 2)]) - eq_(bbox, (0.0, -90.0, 180.0, 90.0)) + assert grid == (2, 2) + assert list(tiles) == [(2, 0, 2), (3, 0, 2), (2, 1, 2), (3, 1, 2)] + assert bbox == (0.0, -90.0, 180.0, 90.0) class TestGKTileGrid(TileGridTest): def setup(self): @@ -605,9 +607,15 @@ assert [self.grid.tile(x, y, level) for level in range(5)] == \ [(0, 0, 0), (0, 1, 1), (0, 3, 2), (1, 6, 3), (3, 12, 4)] - def test_grids(self): - for level, grid_size in [(0, (1, 1)), (1, (2, 2)), (2, (4, 4)), (3, (7, 8))]: - yield self.check_grid, level, grid_size + + @pytest.mark.parametrize('level,grid_size', [ + (0, (1, 1)), + (1, (2, 2)), + (2, (4, 4)), + (3, (7, 8)), + ]) + def test_grids(self, level, grid_size): + self.check_grid(level, grid_size) def test_closest_level(self): assert self.grid.closest_level(880000.0/256) == 0 @@ -639,23 +647,23 @@ assert self.grid.bbox == (3300000, 5300000, 3900000, 6000000) def test_tile_bbox(self): - eq_(self.grid.tile_bbox((0, 0, 0)), + assert (self.grid.tile_bbox((0, 0, 0)) == (3300000.0, 5616000.0, 3684000.0, 6000000.0)) - eq_(self.grid.tile_bbox((1, 0, 0)), + assert (self.grid.tile_bbox((1, 0, 0)) == (3684000.0, 5616000.0, 4068000.0, 6000000.0)) - eq_(self.grid.tile_bbox((1, 1, 0)), + assert (self.grid.tile_bbox((1, 1, 0)) == (3684000.0, 5232000.0, 4068000.0, 5616000.0)) def test_tile(self): x, y = 3310000, 5990000 - eq_(self.grid.tile(x, y, 0), (0, 0, 0)) - eq_(self.grid.tile(x, y, 1), (0, 0, 1)) - eq_(self.grid.tile(x, y, 2), (0, 0, 2)) + assert (self.grid.tile(x, y, 0) == (0, 0, 0)) + assert (self.grid.tile(x, y, 1) == (0, 0, 1)) + assert (self.grid.tile(x, y, 2) == (0, 0, 2)) x, y = 3890000, 5310000 - eq_(self.grid.tile(x, y, 0), (1, 1, 0)) - eq_(self.grid.tile(x, y, 1), (2, 2, 1)) - eq_(self.grid.tile(x, y, 2), (4, 5, 2)) + assert (self.grid.tile(x, y, 0) == (1, 1, 0)) + assert (self.grid.tile(x, y, 1) == (2, 2, 1)) + assert (self.grid.tile(x, y, 2) == (4, 5, 2)) def test_grids(self): assert_grid_size(self.grid, 0, (2, 2)) @@ -687,12 +695,12 @@ ) def test_closest_level(self): - eq_(self.grid.closest_level(5000), 0) - eq_(self.grid.closest_level(4000), 0) - eq_(self.grid.closest_level(3750), 1) - eq_(self.grid.closest_level(3500), 2) - eq_(self.grid.closest_level(3250), 3) - eq_(self.grid.closest_level(3000), 4) + assert (self.grid.closest_level(5000) == 0) + assert (self.grid.closest_level(4000) == 0) + assert (self.grid.closest_level(3750) == 1) + assert (self.grid.closest_level(3500) == 2) + assert (self.grid.closest_level(3250) == 3) + assert (self.grid.closest_level(3000) == 4) class TestOrigins(object): @@ -812,9 +820,14 @@ pass else: assert False, 'got no exception' - def test_grid(self): - for level, grid_size in [(0, (3, 4)), (1, (6, 7)), (2, (14, 18))]: - yield self.check_grid, level, grid_size + + @pytest.mark.parametrize('level,grid_size', [ + (0, (3, 4)), + (1, (6, 7)), + (2, (14, 18)), + ]) + def test_grids(self, level, grid_size): + self.check_grid(level, grid_size) def test_tile_bbox(self): tile_bbox = self.grid.tile_bbox((0, 0, 0)) # w: 1000x256 @@ -833,9 +846,13 @@ assert tile_bbox == (-10, 30, 10, 50) assert grid.resolution(0) == 1.0 - def test_grid(self): - for level, grid_size in [(0, (1, 1)), (1, (2, 1)), (2, (4, 2))]: - yield self.check_grid, level, grid_size + @pytest.mark.parametrize('level,grid_size', [ + (0, (1, 1)), + (1, (2, 1)), + (2, (4, 2)), + ]) + def test_grids(self, level, grid_size): + self.check_grid(level, grid_size) def test_adjacent_tile_bbox(self): grid = TileGrid(is_geodetic=True, bbox=(-10, 30, 10, 40), tile_size=(20, 20)) @@ -875,7 +892,7 @@ class TestTileGrid(object): def test_tile_out_of_grid_bounds(self): grid = TileGrid(is_geodetic=True) - eq_(grid.tile(-180.01, 50, 1), (-1, 0, 1)) + assert (grid.tile(-180.01, 50, 1) == (-1, 0, 1)) def test_affected_tiles_out_of_grid_bounds(self): grid = TileGrid() @@ -883,10 +900,11 @@ req_bbox = (-30056262.509599999, -10018754.170400001, -20037508.339999996, -0.00080000050365924835) bbox, grid_size, tiles = \ grid.get_affected_tiles(req_bbox, (256, 256)) - assert_almost_equal_bbox(bbox, req_bbox) - eq_(grid_size, (1, 1)) + assert_almost_equal_bbox(bbox, req_bbox, 1) + assert grid_size == (1, 1) tiles = list(tiles) - eq_(tiles, [None]) + assert tiles == [None] + def test_broken_bbox(self): grid = TileGrid() # broken request from "ArcGIS Client Using WinInet" @@ -903,51 +921,51 @@ # thresholds near the next lower res value grid = TileGrid(res=[1000, 500, 250, 100, 50], threshold_res=[300, 110]) grid.stretch_factor = 1.1 - eq_(grid.closest_level(1100), 0) + assert grid.closest_level(1100) == 0 # regular transition (w/stretchfactor) - eq_(grid.closest_level(950), 0) - eq_(grid.closest_level(800), 1) - eq_(grid.closest_level(500), 1) + assert grid.closest_level(950) == 0 + assert grid.closest_level(800) == 1 + assert grid.closest_level(500) == 1 # transition at threshold - eq_(grid.closest_level(301), 1) - eq_(grid.closest_level(300), 2) - eq_(grid.closest_level(250), 2) + assert grid.closest_level(301) == 1 + assert grid.closest_level(300) == 2 + assert grid.closest_level(250) == 2 # transition at threshold - eq_(grid.closest_level(111), 2) - eq_(grid.closest_level(110), 3) - eq_(grid.closest_level(100), 3) + assert grid.closest_level(111) == 2 + assert grid.closest_level(110) == 3 + assert grid.closest_level(100) == 3 # regular transition (w/stretchfactor) - eq_(grid.closest_level(92), 3) - eq_(grid.closest_level(90), 4) + assert grid.closest_level(92) == 3 + assert grid.closest_level(90) == 4 def test_upper_bound(self): # thresholds near the next upper res value (within threshold) grid = TileGrid(res=[1000, 500, 250, 100, 50], threshold_res=[495, 240]) grid.stretch_factor = 1.1 - eq_(grid.closest_level(1100), 0) + assert (grid.closest_level(1100) == 0) # regular transition (w/stretchfactor) - eq_(grid.closest_level(950), 0) - eq_(grid.closest_level(800), 1) - eq_(grid.closest_level(500), 1) + assert (grid.closest_level(950) == 0) + assert (grid.closest_level(800) == 1) + assert (grid.closest_level(500) == 1) # transition at threshold - eq_(grid.closest_level(496), 1) - eq_(grid.closest_level(495), 2) - eq_(grid.closest_level(250), 2) + assert (grid.closest_level(496) == 1) + assert (grid.closest_level(495) == 2) + assert (grid.closest_level(250) == 2) # transition at threshold (within strechfactor) - eq_(grid.closest_level(241), 2) - eq_(grid.closest_level(240), 3) - eq_(grid.closest_level(100), 3) + assert (grid.closest_level(241) == 2) + assert (grid.closest_level(240) == 3) + assert (grid.closest_level(100) == 3) # regular transition (w/stretchfactor) - eq_(grid.closest_level(92), 3) - eq_(grid.closest_level(90), 4) + assert (grid.closest_level(92) == 3) + assert (grid.closest_level(90) == 4) def test_above_first_res(self): grid = TileGrid(res=[1000, 500, 250, 100, 50], threshold_res=[1100, 750]) grid.stretch_factor = 1.1 - eq_(grid.closest_level(1200), 0) - eq_(grid.closest_level(1100), 0) - eq_(grid.closest_level(1000), 0) - eq_(grid.closest_level(800), 0) - eq_(grid.closest_level(750.1), 0) - eq_(grid.closest_level(750), 1) + assert (grid.closest_level(1200) == 0) + assert (grid.closest_level(1100) == 0) + assert (grid.closest_level(1000) == 0) + assert (grid.closest_level(800) == 0) + assert (grid.closest_level(750.1) == 0) + assert (grid.closest_level(750) == 1) class TestCreateTileList(object): @@ -962,7 +980,7 @@ None, (0, 0, 3), None, None, (0, 1, 3), None, None, None, None] - eq_(expected, tiles) + assert expected == tiles def _create_tile_list(self, xs, ys, level, grid_size): x_limit = grid_size[0] @@ -1056,9 +1074,8 @@ assert not bbox_contains(b1, b2) assert not bbox_contains(b2, b1) -def assert_almost_equal_bbox(bbox1, bbox2, places=2): - for coord1, coord2 in zip(bbox1, bbox2): - assert_almost_equal(coord1, coord2, places, msg='%s != %s' % (bbox1, bbox2)) +def assert_almost_equal_bbox(bbox1, bbox2, rel=0.01): + assert bbox1 == pytest.approx(bbox2, rel=rel) class TestResolutionRange(object): def test_meter(self): @@ -1109,54 +1126,53 @@ def test_from_scale(self): res_range = resolution_range(max_scale=1e6, min_scale=1e3) - assert_almost_equal(res_range.min_res, 280) - assert_almost_equal(res_range.max_res, 0.28) + assert res_range.min_res == pytest.approx(280) + assert res_range.max_res == pytest.approx(0.28) - @raises(ValueError) - def check_invalid_combination(self, min_res, max_res, max_scale, min_scale): - resolution_range(min_res, max_res, max_scale, min_scale) - - def test_invalid_combinations(self): - yield self.check_invalid_combination, 10, None, 10, None - yield self.check_invalid_combination, 10, 20, 10, None - yield self.check_invalid_combination, 10, None, 10, 20 - yield self.check_invalid_combination, 10, 20, 10, 20 + @pytest.mark.parametrize('min_res,max_res,max_scale,min_scale', [ + [10, None, 10, None], + [10, 20, 10, None], + [10, None, 10, 20], + [10, 20, 10, 20], + ]) + def test_invalid_combination(self, min_res, max_res, max_scale, min_scale): + with pytest.raises(ValueError): + resolution_range(min_res, max_res, max_scale, min_scale) - @raises(AssertionError) def test_wrong_order_res(self): - resolution_range(min_res=10, max_res=100) + with pytest.raises(AssertionError): + resolution_range(min_res=10, max_res=100) - @raises(AssertionError) def test_wrong_order_scale(self): - resolution_range(min_scale=100, max_scale=10) - + with pytest.raises(AssertionError): + resolution_range(min_scale=100, max_scale=10) def test_merge_resolutions(self): res_range = merge_resolution_range( ResolutionRange(None, 10), ResolutionRange(1000, None)) - eq_(res_range, None) + assert res_range == None res_range = merge_resolution_range( ResolutionRange(10000, 10), ResolutionRange(1000, None)) - eq_(res_range.min_res, 10000) - eq_(res_range.max_res, None) + assert res_range.min_res == 10000 + assert res_range.max_res == None res_range = merge_resolution_range( ResolutionRange(10000, 10), ResolutionRange(1000, 1)) - eq_(res_range.min_res, 10000) - eq_(res_range.max_res, 1) + assert res_range.min_res == 10000 + assert res_range.max_res == 1 res_range = merge_resolution_range( ResolutionRange(10000, 10), ResolutionRange(None, None)) - eq_(res_range, None) + assert res_range == None res_range = merge_resolution_range( None, ResolutionRange(None, None)) - eq_(res_range, None) + assert res_range == None res_range = merge_resolution_range( ResolutionRange(10000, 10), None) - eq_(res_range, None) + assert res_range == None def test_eq(self): assert resolution_range(None, None) == resolution_range(None, None) diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_image_mask.py mapproxy-1.12.0/mapproxy/test/unit/test_image_mask.py --- mapproxy-1.11.0/mapproxy/test/unit/test_image_mask.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_image_mask.py 2019-08-30 07:34:08.000000000 +0000 @@ -14,113 +14,168 @@ # limitations under the License. from mapproxy.compat.image import Image, ImageDraw -from mapproxy.srs import SRS from mapproxy.image import ImageSource -from mapproxy.image.opts import ImageOptions from mapproxy.image.mask import mask_image_source_from_coverage from mapproxy.image.merge import LayerMerger -from mapproxy.util.coverage import load_limited_to +from mapproxy.image.opts import ImageOptions +from mapproxy.srs import SRS from mapproxy.test.image import assert_img_colors_eq, create_image -from nose.tools import eq_ +from mapproxy.util.coverage import load_limited_to + +import pytest try: from shapely.geometry import Polygon + geom_support = True except ImportError: geom_support = False -if not geom_support: - from nose.plugins.skip import SkipTest - raise SkipTest('requires Shapely') +pytestmark = pytest.mark.skipif(not geom_support, reason="requires shapely") + +def coverage(geom, srs="EPSG:4326"): + return load_limited_to({"srs": srs, "geometry": geom}) -def coverage(geom, srs='EPSG:4326'): - return load_limited_to({'srs': srs, 'geometry': geom}) class TestMaskImage(object): + def test_mask_outside_of_image_transparent(self): - img = ImageSource(Image.new('RGB', (100, 100), color=(100, 0, 200)), - image_opts=ImageOptions(transparent=True)) - result = mask_image_source_from_coverage(img, [0, 0, 10, 10], SRS(4326), coverage([20, 20, 30, 30])) - assert_img_colors_eq(result.as_image().getcolors(), [((100*100), (255, 255, 255, 0))]) + img = ImageSource( + Image.new("RGB", (100, 100), color=(100, 0, 200)), + image_opts=ImageOptions(transparent=True), + ) + result = mask_image_source_from_coverage( + img, [0, 0, 10, 10], SRS(4326), coverage([20, 20, 30, 30]) + ) + assert_img_colors_eq( + result.as_image().getcolors(), [((100 * 100), (255, 255, 255, 0))] + ) def test_mask_outside_of_image_bgcolor(self): - img = ImageSource(Image.new('RGB', (100, 100), color=(100, 0, 200)), - image_opts=ImageOptions(bgcolor=(200, 30, 120))) - - result = mask_image_source_from_coverage(img, [0, 0, 10, 10], SRS(4326), coverage([20, 20, 30, 30])) - assert_img_colors_eq(result.as_image().getcolors(), [((100*100), (200, 30, 120))]) + img = ImageSource( + Image.new("RGB", (100, 100), color=(100, 0, 200)), + image_opts=ImageOptions(bgcolor=(200, 30, 120)), + ) + + result = mask_image_source_from_coverage( + img, [0, 0, 10, 10], SRS(4326), coverage([20, 20, 30, 30]) + ) + assert_img_colors_eq( + result.as_image().getcolors(), [((100 * 100), (200, 30, 120))] + ) def test_mask_partial_image_bgcolor(self): - img = ImageSource(Image.new('RGB', (100, 100), color=(100, 0, 200)), - image_opts=ImageOptions(bgcolor=(200, 30, 120))) - - result = mask_image_source_from_coverage(img, [0, 0, 10, 10], SRS(4326), coverage([5, 5, 30, 30])) - assert_img_colors_eq(result.as_image().getcolors(), - [(7500, (200, 30, 120)), (2500, (100, 0, 200))]) + img = ImageSource( + Image.new("RGB", (100, 100), color=(100, 0, 200)), + image_opts=ImageOptions(bgcolor=(200, 30, 120)), + ) + + result = mask_image_source_from_coverage( + img, [0, 0, 10, 10], SRS(4326), coverage([5, 5, 30, 30]) + ) + assert_img_colors_eq( + result.as_image().getcolors(), + [(7500, (200, 30, 120)), (2500, (100, 0, 200))], + ) def test_mask_partial_image_transparent(self): - img = ImageSource(Image.new('RGB', (100, 100), color=(100, 0, 200)), - image_opts=ImageOptions(transparent=True)) - - result = mask_image_source_from_coverage(img, [0, 0, 10, 10], SRS(4326), coverage([5, 5, 30, 30])) - assert_img_colors_eq(result.as_image().getcolors(), - [(7500, (255, 255, 255, 0)), (2500, (100, 0, 200, 255))]) + img = ImageSource( + Image.new("RGB", (100, 100), color=(100, 0, 200)), + image_opts=ImageOptions(transparent=True), + ) + + result = mask_image_source_from_coverage( + img, [0, 0, 10, 10], SRS(4326), coverage([5, 5, 30, 30]) + ) + assert_img_colors_eq( + result.as_image().getcolors(), + [(7500, (255, 255, 255, 0)), (2500, (100, 0, 200, 255))], + ) def test_wkt_mask_partial_image_transparent(self): - img = ImageSource(Image.new('RGB', (100, 100), color=(100, 0, 200)), - image_opts=ImageOptions(transparent=True)) + img = ImageSource( + Image.new("RGB", (100, 100), color=(100, 0, 200)), + image_opts=ImageOptions(transparent=True), + ) # polygon with hole - geom = 'POLYGON((2 2, 2 8, 8 8, 8 2, 2 2), (4 4, 4 6, 6 6, 6 4, 4 4))' + geom = "POLYGON((2 2, 2 8, 8 8, 8 2, 2 2), (4 4, 4 6, 6 6, 6 4, 4 4))" - result = mask_image_source_from_coverage(img, [0, 0, 10, 10], SRS(4326), coverage(geom)) + result = mask_image_source_from_coverage( + img, [0, 0, 10, 10], SRS(4326), coverage(geom) + ) # 60*60 - 20*20 = 3200 - assert_img_colors_eq(result.as_image().getcolors(), - [(10000-3200, (255, 255, 255, 0)), (3200, (100, 0, 200, 255))]) + assert_img_colors_eq( + result.as_image().getcolors(), + [(10000 - 3200, (255, 255, 255, 0)), (3200, (100, 0, 200, 255))], + ) def test_shapely_mask_with_transform_partial_image_transparent(self): - img = ImageSource(Image.new('RGB', (100, 100), color=(100, 0, 200)), - image_opts=ImageOptions(transparent=True)) - - p = Polygon([(0, 0), (222000, 0), (222000, 222000), (0, 222000)]) # ~ 2x2 degres - - result = mask_image_source_from_coverage(img, [0, 0, 10, 10], SRS(4326), coverage(p, 'EPSG:3857')) + img = ImageSource( + Image.new("RGB", (100, 100), color=(100, 0, 200)), + image_opts=ImageOptions(transparent=True), + ) + + p = Polygon( + [(0, 0), (222000, 0), (222000, 222000), (0, 222000)] + ) # ~ 2x2 degres + + result = mask_image_source_from_coverage( + img, [0, 0, 10, 10], SRS(4326), coverage(p, "EPSG:3857") + ) # 20*20 = 400 - assert_img_colors_eq(result.as_image().getcolors(), - [(10000-400, (255, 255, 255, 0)), (400, (100, 0, 200, 255))]) + assert_img_colors_eq( + result.as_image().getcolors(), + [(10000 - 400, (255, 255, 255, 0)), (400, (100, 0, 200, 255))], + ) class TestLayerCoverageMerge(object): + def setup(self): - self.coverage1 = coverage(Polygon([(0, 0), (0, 10), (10, 10), (10, 0)]), 3857) + self.coverage1 = coverage( + Polygon([(0, 0), (0, 10), (10, 10), (10, 0)]), 3857 + ) self.coverage2 = coverage([2, 2, 8, 8], 3857) def test_merge_single_coverage(self): merger = LayerMerger() - merger.add(ImageSource(Image.new('RGB', (10, 10), (255, 255, 255))), self.coverage1) - result = merger.merge(image_opts=ImageOptions(transparent=True), bbox=(5, 0, 15, 10), bbox_srs=3857) + merger.add( + ImageSource(Image.new("RGB", (10, 10), (255, 255, 255))), + self.coverage1, + ) + result = merger.merge( + image_opts=ImageOptions(transparent=True), + bbox=(5, 0, 15, 10), + bbox_srs=3857, + ) img = result.as_image() - eq_(img.mode, 'RGBA') - eq_(img.getpixel((4, 0)), (255, 255, 255, 255)) - eq_(img.getpixel((6, 0)), (255, 255, 255, 0)) + assert img.mode == "RGBA" + assert img.getpixel((4, 0)) == (255, 255, 255, 255) + assert img.getpixel((6, 0)) == (255, 255, 255, 0) def test_merge_overlapping_coverage(self): color1 = (255, 255, 0) color2 = (0, 255, 255) merger = LayerMerger() - merger.add(ImageSource(Image.new('RGB', (10, 10), color1)), self.coverage1) - merger.add(ImageSource(Image.new('RGB', (10, 10), color2)), self.coverage2) - - result = merger.merge(image_opts=ImageOptions(), bbox=(0, 0, 10, 10), bbox_srs=3857) + merger.add( + ImageSource(Image.new("RGB", (10, 10), color1)), self.coverage1 + ) + merger.add( + ImageSource(Image.new("RGB", (10, 10), color2)), self.coverage2 + ) + + result = merger.merge( + image_opts=ImageOptions(), bbox=(0, 0, 10, 10), bbox_srs=3857 + ) img = result.as_image() - eq_(img.mode, 'RGB') + assert img.mode == "RGB" - expected = create_image((10, 10), color1, 'RGB') + expected = create_image((10, 10), color1, "RGB") draw = ImageDraw.Draw(expected) draw.polygon([(2, 2), (7, 2), (7, 7), (2, 7)], fill=color2) for x in range(0, 9): for y in range(0, 9): - eq_(img.getpixel((x, y)), expected.getpixel((x, y))) - + assert img.getpixel((x, y)) == expected.getpixel((x, y)) diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_image_messages.py mapproxy-1.12.0/mapproxy/test/unit/test_image_messages.py --- mapproxy-1.11.0/mapproxy/test/unit/test_image_messages.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_image_messages.py 2019-08-30 07:34:08.000000000 +0000 @@ -16,137 +16,152 @@ from __future__ import print_function -from mapproxy.compat.image import ( - Image, - ImageDraw, - ImageColor, - ImageFont, -) - -from mapproxy.compat import PY3 +from mapproxy.compat.image import Image, ImageDraw, ImageColor, ImageFont from mapproxy.cache.tile import Tile from mapproxy.image import ImageSource from mapproxy.image.message import TextDraw, message_image from mapproxy.image.opts import ImageOptions from mapproxy.tilefilter import watermark_filter -from nose.tools import eq_ -from nose.plugins.skip import SkipTest -PNG_FORMAT = ImageOptions(format='image/png') +PNG_FORMAT = ImageOptions(format="image/png") + class TestTextDraw(object): + def test_ul(self): font = ImageFont.load_default() - td = TextDraw('Hello', font) - img = Image.new('RGB', (100, 100)) + td = TextDraw("Hello", font) + img = Image.new("RGB", (100, 100)) draw = ImageDraw.Draw(img) total_box, boxes = td.text_boxes(draw, (100, 100)) - eq_(total_box, boxes[0]) - eq_(len(boxes), 1) + assert total_box == boxes[0] + assert len(boxes) == 1 def test_multiline_ul(self): font = ImageFont.load_default() - td = TextDraw('Hello\nWorld', font) - img = Image.new('RGB', (100, 100)) + td = TextDraw("Hello\nWorld", font) + img = Image.new("RGB", (100, 100)) draw = ImageDraw.Draw(img) total_box, boxes = td.text_boxes(draw, (100, 100)) - eq_(total_box, (5, 5, 35, 30)) - eq_(boxes, [(5, 5, 35, 16), (5, 19, 35, 30)]) + assert total_box == (5, 5, 35, 30) + assert boxes == [(5, 5, 35, 16), (5, 19, 35, 30)] def test_multiline_lr(self): font = ImageFont.load_default() - td = TextDraw('Hello\nWorld', font, placement='lr') - img = Image.new('RGB', (100, 100)) + td = TextDraw("Hello\nWorld", font, placement="lr") + img = Image.new("RGB", (100, 100)) draw = ImageDraw.Draw(img) total_box, boxes = td.text_boxes(draw, (100, 100)) - eq_(total_box, (65, 70, 95, 95)) - eq_(boxes, [(65, 70, 95, 81), (65, 84, 95, 95)]) + assert total_box == (65, 70, 95, 95) + assert boxes == [(65, 70, 95, 81), (65, 84, 95, 95)] def test_multiline_center(self): font = ImageFont.load_default() - td = TextDraw('Hello\nWorld', font, placement='cc') - img = Image.new('RGB', (100, 100)) + td = TextDraw("Hello\nWorld", font, placement="cc") + img = Image.new("RGB", (100, 100)) draw = ImageDraw.Draw(img) total_box, boxes = td.text_boxes(draw, (100, 100)) - eq_(total_box, (35, 38, 65, 63)) - eq_(boxes, [(35, 38, 65, 49), (35, 52, 65, 63)]) + assert total_box == (35, 38, 65, 63) + assert boxes == [(35, 38, 65, 49), (35, 52, 65, 63)] def test_unicode(self): font = ImageFont.load_default() - td = TextDraw(u'Héllö\nWørld', font, placement='cc') - img = Image.new('RGB', (100, 100)) + td = TextDraw(u"Héllö\nWørld", font, placement="cc") + img = Image.new("RGB", (100, 100)) draw = ImageDraw.Draw(img) total_box, boxes = td.text_boxes(draw, (100, 100)) - if PY3: - raise SkipTest('unicode handling for default font differs on PY3') - eq_(total_box, (35, 38, 65, 63)) - eq_(boxes, [(35, 38, 65, 49), (35, 52, 65, 63)]) + assert total_box == (35, 38, 65, 63) + assert boxes == [(35, 38, 65, 49), (35, 52, 65, 63)] def _test_all(self): - for x in 'c': - for y in 'LR': + for x in "c": + for y in "LR": yield self.check_placement, x, y def check_placement(self, x, y): font = ImageFont.load_default() - td = TextDraw('Hello\nWorld\n%s %s' % (x, y), font, placement=x+y, - padding=5, linespacing=2) - img = Image.new('RGB', (100, 100)) + td = TextDraw( + "Hello\nWorld\n%s %s" % (x, y), + font, + placement=x + y, + padding=5, + linespacing=2, + ) + img = Image.new("RGB", (100, 100)) draw = ImageDraw.Draw(img) td.draw(draw, img.size) img.show() def test_transparent(self): font = ImageFont.load_default() - td = TextDraw('Hello\nWorld', font, placement='cc') - img = Image.new('RGBA', (100, 100), (0, 0, 0, 0)) + td = TextDraw("Hello\nWorld", font, placement="cc") + img = Image.new("RGBA", (100, 100), (0, 0, 0, 0)) draw = ImageDraw.Draw(img) td.draw(draw, img.size) - eq_(len(img.getcolors()), 2) + assert len(img.getcolors()) == 2 # top color (bg) is transparent - eq_(sorted(img.getcolors())[1][1], (0, 0, 0, 0)) + assert sorted(img.getcolors())[1][1] == (0, 0, 0, 0) class TestMessageImage(object): + def test_blank(self): image_opts = PNG_FORMAT.copy() - image_opts.bgcolor = '#113399' - img = message_image('', size=(100, 150), image_opts=image_opts) + image_opts.bgcolor = "#113399" + img = message_image("", size=(100, 150), image_opts=image_opts) assert isinstance(img, ImageSource) - eq_(img.size, (100, 150)) + assert img.size == (100, 150) pil_img = img.as_image() - eq_(pil_img.getpixel((0, 0)), ImageColor.getrgb('#113399')) + assert pil_img.getpixel((0, 0)) == ImageColor.getrgb("#113399") # 3 values in histogram (RGB) - assert [x for x in pil_img.histogram() if x > 0] == [15000, 15000, 15000] + assert [x for x in pil_img.histogram() if x > 0] == [ + 15000, + 15000, + 15000, + ] + def test_message(self): image_opts = PNG_FORMAT.copy() - image_opts.bgcolor = '#113399' - img = message_image('test', size=(100, 150), image_opts=image_opts) + image_opts.bgcolor = "#113399" + img = message_image("test", size=(100, 150), image_opts=image_opts) assert isinstance(img, ImageSource) assert img.size == (100, 150) # 6 values in histogram (3xRGB for background, 3xRGB for text message) - eq_([x for x in img.as_image().histogram() if x > 10], - [14923, 77, 14923, 77, 14923, 77]) + assert [x for x in img.as_image().histogram() if x > 10] == [ + 14923, + 77, + 14923, + 77, + 14923, + 77, + ] + def test_transparent(self): image_opts = ImageOptions(transparent=True) print(image_opts) - img = message_image('', size=(100, 150), image_opts=image_opts) + img = message_image("", size=(100, 150), image_opts=image_opts) assert isinstance(img, ImageSource) assert img.size == (100, 150) pil_img = img.as_image() - eq_(pil_img.getpixel((0, 0)), (255, 255, 255, 0)) + assert pil_img.getpixel((0, 0)) == (255, 255, 255, 0) # 6 values in histogram (3xRGB for background, 3xRGB for text message) - assert [x for x in pil_img.histogram() if x > 0] == \ - [15000, 15000, 15000, 15000] + assert [x for x in pil_img.histogram() if x > 0] == [ + 15000, + 15000, + 15000, + 15000, + ] class TestWatermarkTileFilter(object): + def setup(self): self.tile = Tile((0, 0, 0)) - self.filter = watermark_filter('Test') + self.filter = watermark_filter("Test") + def test_filter(self): - img = Image.new('RGB', (200, 200)) + img = Image.new("RGB", (200, 200)) orig_source = ImageSource(img) self.tile.source = orig_source filtered_tile = self.filter(self.tile) @@ -155,7 +170,7 @@ assert orig_source != filtered_tile.source pil_img = filtered_tile.source.as_image() - eq_(pil_img.getpixel((0, 0)), (0, 0, 0)) + assert pil_img.getpixel((0, 0)) == (0, 0, 0) colors = pil_img.getcolors() colors.sort() @@ -164,7 +179,7 @@ assert colors[-1][1] == (0, 0, 0) def test_filter_with_alpha(self): - img = Image.new('RGBA', (200, 200), (10, 15, 20, 0)) + img = Image.new("RGBA", (200, 200), (10, 15, 20, 0)) orig_source = ImageSource(img) self.tile.source = orig_source filtered_tile = self.filter(self.tile) @@ -173,10 +188,10 @@ assert orig_source != filtered_tile.source pil_img = filtered_tile.source.as_image() - eq_(pil_img.getpixel((0, 0)), (10, 15, 20, 0)) + assert pil_img.getpixel((0, 0)) == (10, 15, 20, 0) colors = pil_img.getcolors() colors.sort() # most but not all parts are bg color assert 39950 > colors[-1][0] > 39000 - eq_(colors[-1][1], (10, 15, 20, 0)) \ No newline at end of file + assert colors[-1][1] == (10, 15, 20, 0) diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_image_options.py mapproxy-1.12.0/mapproxy/test/unit/test_image_options.py --- mapproxy-1.11.0/mapproxy/test/unit/test_image_options.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_image_options.py 2019-08-30 07:34:08.000000000 +0000 @@ -14,147 +14,147 @@ # limitations under the License. -from mapproxy.image.opts import ImageOptions, create_image, compatible_image_options -from nose.tools import eq_ +from mapproxy.image.opts import ( + ImageOptions, + create_image, + compatible_image_options, +) + class TestCreateImage(object): + def test_default(self): img = create_image((100, 100)) - eq_(img.size, (100, 100)) - eq_(img.mode, 'RGB') - eq_(img.getcolors(), [(100*100, (255, 255, 255))]) + assert img.size == (100, 100) + assert img.mode == "RGB" + assert img.getcolors() == [(100 * 100, (255, 255, 255))] def test_transparent(self): img = create_image((100, 100), ImageOptions(transparent=True)) - eq_(img.size, (100, 100)) - eq_(img.mode, 'RGBA') - eq_(img.getcolors(), [(100*100, (255, 255, 255, 0))]) + assert img.size == (100, 100) + assert img.mode == "RGBA" + assert img.getcolors() == [(100 * 100, (255, 255, 255, 0))] def test_transparent_rgb(self): - img = create_image((100, 100), ImageOptions(mode='RGB', transparent=True)) - eq_(img.size, (100, 100)) - eq_(img.mode, 'RGB') - eq_(img.getcolors(), [(100*100, (255, 255, 255))]) + img = create_image( + (100, 100), ImageOptions(mode="RGB", transparent=True) + ) + assert img.size == (100, 100) + assert img.mode == "RGB" + assert img.getcolors() == [(100 * 100, (255, 255, 255))] def test_bgcolor(self): img = create_image((100, 100), ImageOptions(bgcolor=(200, 100, 0))) - eq_(img.size, (100, 100)) - eq_(img.mode, 'RGB') - eq_(img.getcolors(), [(100*100, (200, 100, 0))]) + assert img.size == (100, 100) + assert img.mode == "RGB" + assert img.getcolors() == [(100 * 100, (200, 100, 0))] def test_rgba_bgcolor(self): img = create_image((100, 100), ImageOptions(bgcolor=(200, 100, 0, 30))) - eq_(img.size, (100, 100)) - eq_(img.mode, 'RGB') - eq_(img.getcolors(), [(100*100, (200, 100, 0))]) + assert img.size == (100, 100) + assert img.mode == "RGB" + assert img.getcolors() == [(100 * 100, (200, 100, 0))] def test_rgba_bgcolor_transparent(self): - img = create_image((100, 100), ImageOptions(bgcolor=(200, 100, 0, 30), transparent=True)) - eq_(img.size, (100, 100)) - eq_(img.mode, 'RGBA') - eq_(img.getcolors(), [(100*100, (200, 100, 0, 30))]) + img = create_image( + (100, 100), + ImageOptions(bgcolor=(200, 100, 0, 30), transparent=True), + ) + assert img.size == (100, 100) + assert img.mode == "RGBA" + assert img.getcolors() == [(100 * 100, (200, 100, 0, 30))] def test_rgba_bgcolor_rgba_mode(self): - img = create_image((100, 100), ImageOptions(bgcolor=(200, 100, 0, 30), mode='RGBA')) - eq_(img.size, (100, 100)) - eq_(img.mode, 'RGBA') - eq_(img.getcolors(), [(100*100, (200, 100, 0, 30))]) + img = create_image( + (100, 100), ImageOptions(bgcolor=(200, 100, 0, 30), mode="RGBA") + ) + assert img.size == (100, 100) + assert img.mode == "RGBA" + assert img.getcolors() == [(100 * 100, (200, 100, 0, 30))] class TestCompatibleImageOptions(object): + def test_formats(self): - img_opts = compatible_image_options([ - ImageOptions(format='image/png'), - ImageOptions(format='image/jpeg'), - ]) - eq_(img_opts.format, 'image/png') - - img_opts = compatible_image_options([ - ImageOptions(format='image/png'), - ImageOptions(format='image/jpeg'), - ], - ImageOptions(format='image/tiff'), + img_opts = compatible_image_options( + [ + ImageOptions(format="image/png"), + ImageOptions(format="image/jpeg"), + ] ) - eq_(img_opts.format, 'image/tiff') + assert img_opts.format == "image/png" + + img_opts = compatible_image_options( + [ + ImageOptions(format="image/png"), + ImageOptions(format="image/jpeg"), + ], + ImageOptions(format="image/tiff"), + ) + assert img_opts.format == "image/tiff" def test_colors(self): - img_opts = compatible_image_options([ - ImageOptions(colors=None), - ImageOptions(colors=16), - ]) - eq_(img_opts.colors, 16) - - img_opts = compatible_image_options([ - ImageOptions(colors=256), - ImageOptions(colors=16), - ]) - eq_(img_opts.colors, 256) - - img_opts = compatible_image_options([ - ImageOptions(colors=256), - ImageOptions(colors=16), - ], - ImageOptions(colors=4) - ) - eq_(img_opts.colors, 4) - - img_opts = compatible_image_options([ - ImageOptions(colors=256), - ImageOptions(colors=0), - ]) - eq_(img_opts.colors, 0) + img_opts = compatible_image_options( + [ImageOptions(colors=None), ImageOptions(colors=16)] + ) + assert img_opts.colors == 16 + + img_opts = compatible_image_options( + [ImageOptions(colors=256), ImageOptions(colors=16)] + ) + assert img_opts.colors == 256 + + img_opts = compatible_image_options( + [ImageOptions(colors=256), ImageOptions(colors=16)], + ImageOptions(colors=4), + ) + assert img_opts.colors == 4 + + img_opts = compatible_image_options( + [ImageOptions(colors=256), ImageOptions(colors=0)] + ) + assert img_opts.colors == 0 def test_transparent(self): - img_opts = compatible_image_options([ - ImageOptions(transparent=False), - ImageOptions(transparent=True), - ]) - eq_(img_opts.transparent, False) + img_opts = compatible_image_options( + [ImageOptions(transparent=False), ImageOptions(transparent=True)] + ) + assert not img_opts.transparent - img_opts = compatible_image_options([ - ImageOptions(transparent=None), - ImageOptions(transparent=True), - ]) - eq_(img_opts.transparent, True) + img_opts = compatible_image_options( + [ImageOptions(transparent=None), ImageOptions(transparent=True)] + ) + assert img_opts.transparent - img_opts = compatible_image_options([ + img_opts = compatible_image_options( + [ImageOptions(transparent=None), ImageOptions(transparent=True)], ImageOptions(transparent=None), - ImageOptions(transparent=True), - ], - ImageOptions(transparent=None) - ) - eq_(img_opts.transparent, True) - - img_opts = compatible_image_options([ - ImageOptions(transparent=True), - ImageOptions(transparent=True), - ]) - eq_(img_opts.transparent, True) + ) + assert img_opts.transparent + + img_opts = compatible_image_options( + [ImageOptions(transparent=True), ImageOptions(transparent=True)] + ) + assert img_opts.transparent def test_mode(self): - img_opts = compatible_image_options([ - ImageOptions(mode='RGB'), - ImageOptions(mode='P'), - ]) - eq_(img_opts.mode, 'RGB') - - img_opts = compatible_image_options([ - ImageOptions(mode='RGBA'), - ImageOptions(mode='P'), - ]) - eq_(img_opts.mode, 'RGBA') - - img_opts = compatible_image_options([ - ImageOptions(mode='RGB'), - ImageOptions(mode='P'), - ]) - eq_(img_opts.mode, 'RGB') - - img_opts = compatible_image_options([ - ImageOptions(mode='RGB'), - ImageOptions(mode='P'), - ], - ImageOptions(mode='P') + img_opts = compatible_image_options( + [ImageOptions(mode="RGB"), ImageOptions(mode="P")] + ) + assert img_opts.mode == "RGB" + + img_opts = compatible_image_options( + [ImageOptions(mode="RGBA"), ImageOptions(mode="P")] ) - eq_(img_opts.mode, 'P') + assert img_opts.mode == "RGBA" + img_opts = compatible_image_options( + [ImageOptions(mode="RGB"), ImageOptions(mode="P")] + ) + assert img_opts.mode == "RGB" + + img_opts = compatible_image_options( + [ImageOptions(mode="RGB"), ImageOptions(mode="P")], + ImageOptions(mode="P"), + ) + assert img_opts.mode == "P" diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_image.py mapproxy-1.12.0/mapproxy/test/unit/test_image.py --- mapproxy-1.11.0/mapproxy/test/unit/test_image.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_image.py 2019-08-30 07:34:08.000000000 +0000 @@ -16,35 +16,52 @@ import os + from io import BytesIO + +import PIL +import pytest + from mapproxy.compat.image import Image, ImageDraw from mapproxy.image import ( - ImageSource, BlankImageSource, + GeoReference, + ImageSource, ReadBufWrapper, - is_single_color_image, - peek_image_format, - _make_transparent as make_transparent, SubImageSource, + TIFF_GEOKEYDIRECTORYTAG, + TIFF_MODELPIXELSCALETAG, + TIFF_MODELTIEPOINTTAG, + _make_transparent as make_transparent, img_has_transparency, + is_single_color_image, + peek_image_format, quantize, ) from mapproxy.image.merge import merge_images, BandMerger from mapproxy.image.opts import ImageOptions from mapproxy.image.tile import TileMerger, TileSplitter from mapproxy.image.transform import ImageTransformer, transform_meshes -from mapproxy.test.image import is_png, is_jpeg, is_tiff, create_tmp_image_file, check_format, create_debug_img, create_image from mapproxy.srs import SRS -from nose.tools import eq_, assert_almost_equal -from mapproxy.test.image import assert_img_colors_eq -from nose.plugins.skip import SkipTest +from mapproxy.test.image import ( + is_png, + is_jpeg, + is_tiff, + create_tmp_image_file, + check_format, + create_debug_img, + create_image, + assert_img_colors_eq, +) -PNG_FORMAT = ImageOptions(format='image/png') -JPEG_FORMAT = ImageOptions(format='image/jpeg') -TIFF_FORMAT = ImageOptions(format='image/tiff') +PNG_FORMAT = ImageOptions(format="image/png") +JPEG_FORMAT = ImageOptions(format="image/jpeg") +TIFF_FORMAT = ImageOptions(format="image/tiff") + class TestImageSource(object): + def setup(self): self.tmp_filename = create_tmp_image_file((100, 100)) @@ -57,19 +74,19 @@ assert ir.as_image().size == (100, 100) def test_from_file(self): - with open(self.tmp_filename, 'rb') as tmp_file: - ir = ImageSource(tmp_file, 'png') + with open(self.tmp_filename, "rb") as tmp_file: + ir = ImageSource(tmp_file, "png") assert ir.as_buffer() == tmp_file assert ir.as_image().size == (100, 100) def test_from_image(self): - img = Image.new('RGBA', (100, 100)) + img = Image.new("RGBA", (100, 100)) ir = ImageSource(img, (100, 100), PNG_FORMAT) assert ir.as_image() == img assert is_png(ir.as_buffer()) def test_from_non_seekable_file(self): - with open(self.tmp_filename, 'rb') as tmp_file: + with open(self.tmp_filename, "rb") as tmp_file: data = tmp_file.read() class FileLikeDummy(object): @@ -77,17 +94,16 @@ def read(self): return data - ir = ImageSource(FileLikeDummy(), 'png') + ir = ImageSource(FileLikeDummy(), "png") assert ir.as_buffer(seekable=True).read() == data assert ir.as_image().size == (100, 100) assert ir.as_buffer().read() == data - def test_output_formats(self): - img = Image.new('RGB', (100, 100)) - for format in ['png', 'gif', 'tiff', 'jpeg', 'GeoTIFF', 'bmp']: + img = Image.new("RGB", (100, 100)) + for format in ["png", "gif", "tiff", "jpeg", "GeoTIFF", "bmp"]: ir = ImageSource(img, (100, 100), image_opts=ImageOptions(format=format)) - yield check_format, ir.as_buffer(), format + check_format(ir.as_buffer(), format) def test_converted_output(self): ir = ImageSource(self.tmp_filename, (100, 100), PNG_FORMAT) @@ -97,118 +113,177 @@ assert is_tiff(ir.as_buffer(TIFF_FORMAT)) assert is_tiff(ir.as_buffer()) + @pytest.mark.skipif(PIL.PILLOW_VERSION < '6.1.0', reason="Pillow 6.1.0 required GeoTIFF") + def test_tiff_compression(self): + def encoded_size(encoding_options): + ir = ImageSource(create_debug_img((100, 100)), PNG_FORMAT) + buf = ir.as_buffer(ImageOptions(format="tiff", encoding_options=encoding_options)) + return len(buf.read()) + + orig = encoded_size({}) + q90 = encoded_size({'tiff_compression': 'jpeg', 'jpeg_quality': 90}) + q75 = encoded_size({'tiff_compression': 'jpeg', 'jpeg_quality': 75}) + qdf = encoded_size({'tiff_compression': 'jpeg'}) + q50 = encoded_size({'tiff_compression': 'jpeg', 'jpeg_quality': 50}) + lzw = encoded_size({'tiff_compression': 'tiff_lzw'}) + + # print(orig, q90, q75, qdf, q50, lzw) + assert orig > q90 + assert q90 > q75 + assert q75 == qdf + assert qdf > q50 + assert q50 > lzw def test_output_formats_greyscale_png(self): - img = Image.new('L', (100, 100)) + img = Image.new("L", (100, 100)) ir = ImageSource(img, image_opts=PNG_FORMAT) - img = Image.open(ir.as_buffer(ImageOptions(colors=256, transparent=True, format='image/png'))) - assert img.mode == 'P' + img = Image.open( + ir.as_buffer(ImageOptions(colors=256, transparent=True, format="image/png")) + ) + assert img.mode == "P" assert img.getpixel((0, 0)) == 255 def test_output_formats_greyscale_alpha_png(self): - img = Image.new('LA', (100, 100)) + img = Image.new("LA", (100, 100)) ir = ImageSource(img, image_opts=PNG_FORMAT) - img = Image.open(ir.as_buffer(ImageOptions(colors=256, transparent=True, format='image/png'))) - assert img.mode == 'LA' + img = Image.open( + ir.as_buffer(ImageOptions(colors=256, transparent=True, format="image/png")) + ) + assert img.mode == "LA" assert img.getpixel((0, 0)) == (0, 0) def test_output_formats_png8(self): - img = Image.new('RGBA', (100, 100)) + img = Image.new("RGBA", (100, 100)) ir = ImageSource(img, image_opts=PNG_FORMAT) - img = Image.open(ir.as_buffer(ImageOptions(colors=256, transparent=True, format='image/png'))) - assert img.mode == 'P' + img = Image.open( + ir.as_buffer(ImageOptions(colors=256, transparent=True, format="image/png")) + ) + assert img.mode == "P" assert img.getpixel((0, 0)) == 255 def test_output_formats_png24(self): - img = Image.new('RGBA', (100, 100)) + img = Image.new("RGBA", (100, 100)) image_opts = PNG_FORMAT.copy() - image_opts.colors = 0 # TODO image_opts + image_opts.colors = 0 # TODO image_opts ir = ImageSource(img, image_opts=image_opts) img = Image.open(ir.as_buffer()) - eq_(img.mode, 'RGBA') + assert img.mode == "RGBA" assert img.getpixel((0, 0)) == (0, 0, 0, 0) def test_save_with_unsupported_transparency(self): # check if encoding of non-RGB image with tuple as transparency # works. workaround for Pillow #2633 - img = Image.new('P', (100, 100)) - img.info['transparency'] = (0, 0, 0) + img = Image.new("P", (100, 100)) + img.info["transparency"] = (0, 0, 0) image_opts = PNG_FORMAT.copy() ir = ImageSource(img, image_opts=image_opts) img = Image.open(ir.as_buffer()) - eq_(img.mode, 'P') + assert img.mode == "P" + class TestSubImageSource(object): + def test_full(self): sub_img = create_image((100, 100), color=[100, 120, 130, 140]) - img = SubImageSource(sub_img, size=(100, 100), offset=(0, 0), image_opts=ImageOptions()).as_image() - eq_(img.getcolors(), [(100*100, (100, 120, 130, 140))]) + img = SubImageSource( + sub_img, size=(100, 100), offset=(0, 0), image_opts=ImageOptions() + ).as_image() + assert img.getcolors() == [(100 * 100, (100, 120, 130, 140))] def test_larger(self): sub_img = create_image((150, 150), color=[100, 120, 130, 140]) - img = SubImageSource(sub_img, size=(100, 100), offset=(0, 0), image_opts=ImageOptions()).as_image() - eq_(img.getcolors(), [(100*100, (100, 120, 130, 140))]) + img = SubImageSource( + sub_img, size=(100, 100), offset=(0, 0), image_opts=ImageOptions() + ).as_image() + assert img.getcolors() == [(100 * 100, (100, 120, 130, 140))] def test_negative_offset(self): sub_img = create_image((150, 150), color=[100, 120, 130, 140]) - img = SubImageSource(sub_img, size=(100, 100), offset=(-50, 0), image_opts=ImageOptions()).as_image() - eq_(img.getcolors(), [(100*100, (100, 120, 130, 140))]) + img = SubImageSource( + sub_img, size=(100, 100), offset=(-50, 0), image_opts=ImageOptions() + ).as_image() + assert img.getcolors() == [(100 * 100, (100, 120, 130, 140))] def test_overlap_right(self): sub_img = create_image((50, 50), color=[100, 120, 130, 140]) - img = SubImageSource(sub_img, size=(100, 100), offset=(75, 25), image_opts=ImageOptions(transparent=True)).as_image() - eq_(sorted(img.getcolors()), [(25*50, (100, 120, 130, 140)), (100*100-25*50, (255, 255, 255, 0))]) + img = SubImageSource( + sub_img, + size=(100, 100), + offset=(75, 25), + image_opts=ImageOptions(transparent=True), + ).as_image() + assert sorted(img.getcolors()) == [ + (25 * 50, (100, 120, 130, 140)), + (100 * 100 - 25 * 50, (255, 255, 255, 0)), + ] def test_outside(self): sub_img = create_image((50, 50), color=[100, 120, 130, 140]) - img = SubImageSource(sub_img, size=(100, 100), offset=(200, 0), image_opts=ImageOptions(transparent=True)).as_image() - eq_(img.getcolors(), [(100*100, (255, 255, 255, 0))]) + img = SubImageSource( + sub_img, + size=(100, 100), + offset=(200, 0), + image_opts=ImageOptions(transparent=True), + ).as_image() + assert img.getcolors() == [(100 * 100, (255, 255, 255, 0))] + class ROnly(object): + def __init__(self): - self.data = [b'Hello World!'] + self.data = [b"Hello World!"] + def read(self): if self.data: return self.data.pop() - return b'' + return b"" + def __iter__(self): it = iter(self.data) self.data = [] return it + class TestReadBufWrapper(object): + def setup(self): rbuf = ROnly() self.rbuf_wrapper = ReadBufWrapper(rbuf) + def test_read(self): - assert self.rbuf_wrapper.read() == b'Hello World!' + assert self.rbuf_wrapper.read() == b"Hello World!" self.rbuf_wrapper.seek(0) - eq_(self.rbuf_wrapper.read(), b'') + assert self.rbuf_wrapper.read() == b"" + def test_seek_read(self): self.rbuf_wrapper.seek(0) - assert self.rbuf_wrapper.read() == b'Hello World!' + assert self.rbuf_wrapper.read() == b"Hello World!" self.rbuf_wrapper.seek(0) - assert self.rbuf_wrapper.read() == b'Hello World!' + assert self.rbuf_wrapper.read() == b"Hello World!" + def test_iter(self): data = list(self.rbuf_wrapper) - eq_(data, [b'Hello World!']) + assert data == [b"Hello World!"] self.rbuf_wrapper.seek(0) data = list(self.rbuf_wrapper) - eq_(data, []) + assert data == [] + def test_seek_iter(self): self.rbuf_wrapper.seek(0) data = list(self.rbuf_wrapper) - eq_(data, [b'Hello World!']) + assert data == [b"Hello World!"] self.rbuf_wrapper.seek(0) data = list(self.rbuf_wrapper) - eq_(data, [b'Hello World!']) + assert data == [b"Hello World!"] + def test_hasattr(self): - assert hasattr(self.rbuf_wrapper, 'seek') - assert hasattr(self.rbuf_wrapper, 'readline') + assert hasattr(self.rbuf_wrapper, "seek") + assert hasattr(self.rbuf_wrapper, "readline") class TestMergeAll(object): + def setup(self): self.cleanup_tiles = [] @@ -219,7 +294,7 @@ img_opts = ImageOptions() result = m.merge(self.tiles, img_opts) img = result.as_image() - eq_(img.size, (300, 300)) + assert img.size == (300, 300) def test_one(self): self.cleanup_tiles = [create_tmp_image_file((100, 100))] @@ -228,53 +303,56 @@ img_opts = ImageOptions(transparent=True) result = m.merge(self.tiles, img_opts) img = result.as_image() - eq_(img.size, (100, 100)) - eq_(img.mode, 'RGBA') + assert img.size == (100, 100) + assert img.mode == "RGBA" def test_missing_tiles(self): self.cleanup_tiles = [create_tmp_image_file((100, 100))] self.tiles = [ImageSource(self.cleanup_tiles[0])] - self.tiles.extend([None]*8) + self.tiles.extend([None] * 8) m = TileMerger(tile_grid=(3, 3), tile_size=(100, 100)) img_opts = ImageOptions() result = m.merge(self.tiles, img_opts) img = result.as_image() - eq_(img.size, (300, 300)) - eq_(img.getcolors(), [(80000, (255, 255, 255)), (10000, (0, 0, 0)), ]) + assert img.size == (300, 300) + assert img.getcolors() == [(80000, (255, 255, 255)), (10000, (0, 0, 0))] def test_invalid_tile(self): self.cleanup_tiles = [create_tmp_image_file((100, 100)) for _ in range(9)] self.tiles = [ImageSource(tile) for tile in self.cleanup_tiles] invalid_tile = self.tiles[0].source - with open(invalid_tile, 'wb') as tmp: - tmp.write(b'invalid') + with open(invalid_tile, "wb") as tmp: + tmp.write(b"invalid") m = TileMerger(tile_grid=(3, 3), tile_size=(100, 100)) img_opts = ImageOptions(bgcolor=(200, 0, 50)) result = m.merge(self.tiles, img_opts) img = result.as_image() - eq_(img.size, (300, 300)) - eq_(img.getcolors(), [(10000, (200, 0, 50)), (80000, (0, 0, 0))]) + assert img.size == (300, 300) + assert img.getcolors() == [(10000, (200, 0, 50)), (80000, (0, 0, 0))] assert not os.path.isfile(invalid_tile) def test_none_merge(self): tiles = [None] m = TileMerger(tile_grid=(1, 1), tile_size=(100, 100)) - img_opts = ImageOptions(mode='RGBA', bgcolor=(200, 100, 30, 40)) + img_opts = ImageOptions(mode="RGBA", bgcolor=(200, 100, 30, 40)) result = m.merge(tiles, img_opts) img = result.as_image() - eq_(img.size, (100, 100)) - eq_(img.getcolors(), [(100*100, (200, 100, 30, 40))]) + assert img.size == (100, 100) + assert img.getcolors() == [(100 * 100, (200, 100, 30, 40))] def teardown(self): for tile_fname in self.cleanup_tiles: if tile_fname and os.path.isfile(tile_fname): os.remove(tile_fname) + class TestGetCrop(object): + def setup(self): self.tmp_file = create_tmp_image_file((100, 100), two_colored=True) - self.img = ImageSource(self.tmp_file, - image_opts=ImageOptions(format='image/png'), size=(100, 100)) + self.img = ImageSource( + self.tmp_file, image_opts=ImageOptions(format="image/png"), size=(100, 100) + ) def teardown(self): if os.path.exists(self.tmp_file): @@ -283,76 +361,91 @@ def test_perfect_match(self): bbox = (-10, -5, 30, 35) transformer = ImageTransformer(SRS(4326), SRS(4326)) - result = transformer.transform(self.img, bbox, (100, 100), bbox, image_opts=None) + result = transformer.transform( + self.img, bbox, (100, 100), bbox, image_opts=None + ) assert self.img == result def test_simple_resize_nearest(self): bbox = (-10, -5, 30, 35) transformer = ImageTransformer(SRS(4326), SRS(4326)) - result = transformer.transform(self.img, bbox, (200, 200), bbox, - image_opts=ImageOptions(resampling='nearest')) + result = transformer.transform( + self.img, + bbox, + (200, 200), + bbox, + image_opts=ImageOptions(resampling="nearest"), + ) img = result.as_image() - eq_(img.size, (200, 200)) - eq_(len(img.getcolors()), 2) + assert img.size == (200, 200) + assert len(img.getcolors()) == 2 + img.close() def test_simple_resize_bilinear(self): bbox = (-10, -5, 30, 35) transformer = ImageTransformer(SRS(4326), SRS(4326)) - result = transformer.transform(self.img, bbox, (200, 200), bbox, - image_opts=ImageOptions(resampling='bilinear')) + result = transformer.transform( + self.img, + bbox, + (200, 200), + bbox, + image_opts=ImageOptions(resampling="bilinear"), + ) img = result.as_image() - eq_(img.size, (200, 200)) + assert img.size == (200, 200) # some shades of grey with bilinear assert len(img.getcolors()) >= 4 + img.close() class TestLayerMerge(object): + def test_opacity_merge(self): - img1 = ImageSource(Image.new('RGB', (10, 10), (255, 0, 255))) - img2 = ImageSource(Image.new('RGB', (10, 10), (0, 255, 255)), - image_opts=ImageOptions(opacity=0.5)) + img1 = ImageSource(Image.new("RGB", (10, 10), (255, 0, 255))) + img2 = ImageSource( + Image.new("RGB", (10, 10), (0, 255, 255)), + image_opts=ImageOptions(opacity=0.5), + ) result = merge_images([img1, img2], ImageOptions(transparent=False)) img = result.as_image() - eq_(img.getpixel((0, 0)), (127, 127, 255)) + assert img.getpixel((0, 0)) == (127, 127, 255) def test_opacity_merge_mixed_modes(self): - img1 = ImageSource(Image.new('RGBA', (10, 10), (255, 0, 255, 255))) - img2 = ImageSource(Image.new('RGB', (10, 10), (0, 255, 255)).convert('P'), - image_opts=ImageOptions(opacity=0.5)) + img1 = ImageSource(Image.new("RGBA", (10, 10), (255, 0, 255, 255))) + img2 = ImageSource( + Image.new("RGB", (10, 10), (0, 255, 255)).convert("P"), + image_opts=ImageOptions(opacity=0.5), + ) result = merge_images([img1, img2], ImageOptions(transparent=True)) img = result.as_image() - assert_img_colors_eq(img, [ - (10*10, (127, 127, 255, 255)), - ]) + assert_img_colors_eq(img, [(10 * 10, (127, 127, 255, 255))]) def test_merge_L(self): - img1 = ImageSource(Image.new('RGBA', (10, 10), (255, 0, 255, 255))) - img2 = ImageSource(Image.new('L', (10, 10), 100)) + img1 = ImageSource(Image.new("RGBA", (10, 10), (255, 0, 255, 255))) + img2 = ImageSource(Image.new("L", (10, 10), 100)) # img2 overlays img1 result = merge_images([img1, img2], ImageOptions(transparent=True)) img = result.as_image() - assert_img_colors_eq(img, [ - (10*10, (100, 100, 100, 255)), - ]) + assert_img_colors_eq(img, [(10 * 10, (100, 100, 100, 255))]) + @pytest.mark.skipif( + not hasattr(Image, "FASTOCTREE"), reason="PIL has no FASTOCTREE" + ) def test_paletted_merge(self): - if not hasattr(Image, 'FASTOCTREE'): - raise SkipTest() - # generate RGBA images with a transparent rectangle in the lower right - img1 = ImageSource(Image.new('RGBA', (50, 50), (0, 255, 0, 255))).as_image() + img1 = ImageSource(Image.new("RGBA", (50, 50), (0, 255, 0, 255))).as_image() draw = ImageDraw.Draw(img1) draw.rectangle((25, 25, 49, 49), fill=(0, 0, 0, 0)) paletted_img = quantize(img1, alpha=True) assert img_has_transparency(paletted_img) - assert paletted_img.mode == 'P' + assert paletted_img.mode == "P" - rgba_img = Image.new('RGBA', (50, 50), (255, 0, 0, 255)) + rgba_img = Image.new("RGBA", (50, 50), (255, 0, 0, 255)) draw = ImageDraw.Draw(rgba_img) draw.rectangle((25, 25, 49, 49), fill=(0, 0, 0, 0)) @@ -360,46 +453,47 @@ img2 = ImageSource(rgba_img) # generate base image and merge the others above - img3 = ImageSource(Image.new('RGBA', (50, 50), (0, 0, 255, 255))) + img3 = ImageSource(Image.new("RGBA", (50, 50), (0, 0, 255, 255))) result = merge_images([img3, img1, img2], ImageOptions(transparent=True)) img = result.as_image() - assert img.mode == 'RGBA' - eq_(img.getpixel((49, 49)), (0, 0, 255, 255)) - eq_(img.getpixel((0, 0)), (255, 0, 0, 255)) + assert img.mode == "RGBA" + assert img.getpixel((49, 49)) == (0, 0, 255, 255) + assert img.getpixel((0, 0)) == (255, 0, 0, 255) def test_solid_merge(self): - img1 = ImageSource(Image.new('RGB', (10, 10), (255, 0, 255))) - img2 = ImageSource(Image.new('RGB', (10, 10), (0, 255, 255))) + img1 = ImageSource(Image.new("RGB", (10, 10), (255, 0, 255))) + img2 = ImageSource(Image.new("RGB", (10, 10), (0, 255, 255))) result = merge_images([img1, img2], ImageOptions(transparent=False)) img = result.as_image() - eq_(img.getpixel((0, 0)), (0, 255, 255)) + assert img.getpixel((0, 0)) == (0, 255, 255) def test_merge_rgb_with_transp(self): - img1 = ImageSource(Image.new('RGB', (10, 10), (255, 0, 255))) - raw = Image.new('RGB', (10, 10), (0, 255, 255)) - raw.info = {'transparency': (0, 255, 255)} # make full transparent + img1 = ImageSource(Image.new("RGB", (10, 10), (255, 0, 255))) + raw = Image.new("RGB", (10, 10), (0, 255, 255)) + raw.info = {"transparency": (0, 255, 255)} # make full transparent img2 = ImageSource(raw) result = merge_images([img1, img2], ImageOptions(transparent=False)) img = result.as_image() - eq_(img.getpixel((0, 0)), (255, 0, 255)) + assert img.getpixel((0, 0)) == (255, 0, 255) +@pytest.mark.skipif( + not hasattr(Image, "alpha_composite"), reason="PIL has no alpha_composite" +) class TestLayerCompositeMerge(object): + def test_composite_merge(self): # http://stackoverflow.com/questions/3374878 - if not hasattr(Image, 'alpha_composite'): - raise SkipTest() - - img1 = Image.new('RGBA', size=(100, 100), color=(255, 0, 0, 255)) + img1 = Image.new("RGBA", size=(100, 100), color=(255, 0, 0, 255)) draw = ImageDraw.Draw(img1) draw.rectangle((33, 0, 66, 100), fill=(255, 0, 0, 128)) draw.rectangle((67, 0, 100, 100), fill=(255, 0, 0, 0)) img1 = ImageSource(img1) - img2 = Image.new('RGBA', size =(100, 100), color=(0, 255, 0, 255)) + img2 = Image.new("RGBA", size=(100, 100), color=(0, 255, 0, 255)) draw = ImageDraw.Draw(img2) draw.rectangle((0, 33, 100, 66), fill=(0, 255, 0, 128)) draw.rectangle((0, 67, 100, 100), fill=(0, 255, 0, 0)) @@ -407,35 +501,38 @@ result = merge_images([img2, img1], ImageOptions(transparent=True)) img = result.as_image() - eq_(img.mode, 'RGBA') - assert_img_colors_eq(img, [ - (1089, (0, 255, 0, 255)), - (1089, (255, 255, 255, 0)), - (1122, (0, 255, 0, 128)), - (1122, (128, 126, 0, 255)), - (1122, (255, 0, 0, 128)), - (1156, (170, 84, 0, 191)), - (3300, (255, 0, 0, 255))]) + assert img.mode == "RGBA" + assert_img_colors_eq( + img, + [ + (1089, (0, 255, 0, 255)), + (1089, (255, 255, 255, 0)), + (1122, (0, 255, 0, 128)), + (1122, (128, 126, 0, 255)), + (1122, (255, 0, 0, 128)), + (1156, (170, 84, 0, 191)), + (3300, (255, 0, 0, 255)), + ], + ) def test_composite_merge_opacity(self): - if not hasattr(Image, 'alpha_composite'): - raise SkipTest() - - bg = Image.new('RGBA', size=(100, 100), color=(255, 0, 255, 255)) + bg = Image.new("RGBA", size=(100, 100), color=(255, 0, 255, 255)) bg = ImageSource(bg) - fg = Image.new('RGBA', size =(100, 100), color=(0, 0, 0, 0)) + fg = Image.new("RGBA", size=(100, 100), color=(0, 0, 0, 0)) draw = ImageDraw.Draw(fg) draw.rectangle((10, 10, 89, 89), fill=(0, 255, 255, 255)) fg = ImageSource(fg, image_opts=ImageOptions(opacity=0.5)) result = merge_images([bg, fg], ImageOptions(transparent=True)) img = result.as_image() - eq_(img.mode, 'RGBA') - assert_img_colors_eq(img, [ - (3600, (255, 0, 255, 255)), - (6400, (128, 127, 255, 255))]) + assert img.mode == "RGBA" + assert_img_colors_eq( + img, [(3600, (255, 0, 255, 255)), (6400, (128, 127, 255, 255))] + ) + class TestTransform(object): + def setup(self): self.src_img = ImageSource(create_debug_img((200, 200), transparent=False)) self.src_srs = SRS(31467) @@ -443,10 +540,16 @@ self.dst_srs = SRS(4326) self.dst_bbox = (0.2, 45.1, 8.3, 53.2) self.src_bbox = self.dst_srs.transform_bbox_to(self.src_srs, self.dst_bbox) + def test_transform(self): transformer = ImageTransformer(self.src_srs, self.dst_srs) - result = transformer.transform(self.src_img, self.src_bbox, self.dst_size, self.dst_bbox, - image_opts=ImageOptions(resampling='nearest')) + result = transformer.transform( + self.src_img, + self.src_bbox, + self.dst_size, + self.dst_bbox, + image_opts=ImageOptions(resampling="nearest"), + ) assert isinstance(result, ImageSource) assert result.as_image() != self.src_img.as_image() assert result.size == (100, 150) @@ -457,10 +560,59 @@ """ for err in [0.2, 0.5, 1, 2, 4, 6, 8, 12, 16]: transformer = ImageTransformer(self.src_srs, self.dst_srs, max_px_err=err) - result = transformer.transform(self.src_img, self.src_bbox, - self.dst_size, self.dst_bbox, - image_opts=ImageOptions(resampling='nearest')) - result.as_image().save('/tmp/transform-%03d.png' % (err*10,)) + result = transformer.transform( + self.src_img, + self.src_bbox, + self.dst_size, + self.dst_bbox, + image_opts=ImageOptions(resampling="nearest"), + ) + result.as_image().save("/tmp/transform-%03d.png" % (err * 10,)) + + +def assert_geotiff_tags(img, expected_origin, expected_pixel_res, srs, projected): + tags = img.tag_v2 + print(dict(tags)) + print(dict(tags.tagtype)) + assert tags[TIFF_MODELTIEPOINTTAG] == ( + 0.0, 0.0, 0.0, expected_origin[0], expected_origin[1], 0.0, + ) + assert tags[TIFF_MODELPIXELSCALETAG] == pytest.approx(( + expected_pixel_res[0], expected_pixel_res[1], 0.0, + )) + assert len(tags[TIFF_GEOKEYDIRECTORYTAG]) == 4*4 + assert tags[TIFF_GEOKEYDIRECTORYTAG][0*4+3] == 4 + assert tags[TIFF_GEOKEYDIRECTORYTAG][1*4+3] == (1 if projected else 2) + assert tags[TIFF_GEOKEYDIRECTORYTAG][3*4+3] == srs + + +@pytest.mark.skipif(PIL.PILLOW_VERSION < '6.1.0', reason="Pillow 6.1.0 required GeoTIFF") +@pytest.mark.parametrize("compression", ['jpeg', 'raw', 'tiff_lzw']) +class TestGeoTIFF(object): + + @pytest.mark.parametrize( + "srs,bbox,size,expected_pixel_res,expected_origin,projected", + [ + (4326, (-180, -90, 180, 90), (360, 180), (1.0, 1.0), (-180, 90), False), + (4326, (-180, -90, 180, 90), (360, 360), (1.0, 0.5), (-180, 90), False), + (3857, (10000, 20000, 11000, 22000), (500, 1000), (2.0, 2.0), (10000, 22000), True), + (25832, (442691.10009850014,5889716.375224128,447502.95988220774,5894528.235007785), + (256, 256), (18.796327, 18.796327), (442691.10009850014, 5894528.235007785), True, + ), + ], + ) + def test_geotiff_tags( + self, tmpdir, srs, bbox, size, + expected_pixel_res, expected_origin, projected, + compression, + ): + img = ImageSource(create_debug_img(size), georef=GeoReference(bbox=bbox, srs=SRS(srs))) + fname = os.path.join(str(tmpdir), 'geo.tiff') + + img_opts = ImageOptions(format='tiff', encoding_options={'tiff_compression': compression}) + img2 = ImageSource(img.as_buffer(img_opts)).as_image() + + assert_geotiff_tags(img2, expected_origin, expected_pixel_res, srs, projected) class TestMesh(object): @@ -474,7 +626,7 @@ dst_bbox=(158512, 4428236, 1012321, 6111268), dst_srs=SRS(25832), ) - eq_(len(meshes), 40) + assert len(meshes) == 40 def test_mesh_none(self): meshes = transform_meshes( @@ -486,9 +638,10 @@ dst_srs=SRS(4326), ) - eq_(meshes, [((0, 0, 1000, 1500), [0.0, 0.0, 0.0, 1500.0, 1000.0, 1500.0, 1000.0, 0.0])]) - eq_(len(meshes), 1) - + assert meshes == [ + ((0, 0, 1000, 1500), [0.0, 0.0, 0.0, 1500.0, 1000.0, 1500.0, 1000.0, 0.0]) + ] + assert len(meshes) == 1 def test_mesh(self): # low map scale -> more meshes @@ -501,31 +654,37 @@ dst_bbox=(5, 50, 10, 55), dst_srs=SRS(4326), ) - eq_(len(meshes), 16) + assert len(meshes) == 16 # large map scale -> one meshes # print(SRS(4326).transform_bbox_to(SRS(3857), (5, 50, 5.1, 50.1))) meshes = transform_meshes( src_size=(1000, 2000), - src_bbox=(556597.4539663672, 6446275.841017158, - 567729.4030456939, 6463612.124257667), + src_bbox=( + 556597.4539663672, + 6446275.841017158, + 567729.4030456939, + 6463612.124257667, + ), src_srs=SRS(3857), dst_size=(1000, 1000), dst_bbox=(5, 50, 5.1, 50.1), dst_srs=SRS(4326), ) - eq_(len(meshes), 1) + assert len(meshes) == 1 # quad stretches whole image plus 1 pixel - eq_(meshes[0][0], (0, 0, 1000, 1000)) - for e, a in zip(meshes[0][1], [0.0, 0.0, 0.0, 2000.0, 1000.0, 2000.0, 1000.0, 0.0]): - assert_almost_equal(e, a) - + assert meshes[0][0] == (0, 0, 1000, 1000) + for e, a in zip( + meshes[0][1], [0.0, 0.0, 0.0, 2000.0, 1000.0, 2000.0, 1000.0, 0.0] + ): + assert e == pytest.approx(a) class TestSingleColorImage(object): + def test_one_point(self): - img = Image.new('RGB', (100, 100), color='#ff0000') + img = Image.new("RGB", (100, 100), color="#ff0000") draw = ImageDraw.Draw(img) draw.point((99, 99)) del draw @@ -533,30 +692,32 @@ assert not is_single_color_image(img) def test_solid(self): - img = Image.new('RGB', (100, 100), color='#ff0102') - eq_(is_single_color_image(img), (255, 1, 2)) + img = Image.new("RGB", (100, 100), color="#ff0102") + assert is_single_color_image(img) == (255, 1, 2) def test_solid_w_alpha(self): - img = Image.new('RGBA', (100, 100), color='#ff0102') - eq_(is_single_color_image(img), (255, 1, 2, 255)) + img = Image.new("RGBA", (100, 100), color="#ff0102") + assert is_single_color_image(img) == (255, 1, 2, 255) def test_solid_paletted_image(self): - img = Image.new('P', (100, 100), color=20) + img = Image.new("P", (100, 100), color=20) palette = [] for i in range(256): - palette.extend((i, i//2, i%3)) + palette.extend((i, i // 2, i % 3)) img.putpalette(palette) - eq_(is_single_color_image(img), (20, 10, 2)) + assert is_single_color_image(img) == (20, 10, 2) + class TestMakeTransparent(object): + def _make_test_image(self): - img = Image.new('RGB', (50, 50), (130, 140, 120)) + img = Image.new("RGB", (50, 50), (130, 140, 120)) draw = ImageDraw.Draw(img) draw.rectangle((10, 10, 39, 39), fill=(130, 150, 120)) return img def _make_transp_test_image(self): - img = Image.new('RGBA', (50, 50), (130, 140, 120, 100)) + img = Image.new("RGBA", (50, 50), (130, 140, 120, 100)) draw = ImageDraw.Draw(img) draw.rectangle((10, 10, 39, 39), fill=(130, 150, 120, 120)) return img @@ -564,7 +725,7 @@ def test_result(self): img = self._make_test_image() img = make_transparent(img, (130, 150, 120), tolerance=5) - assert img.mode == 'RGBA' + assert img.mode == "RGBA" assert img.size == (50, 50) colors = img.getcolors() assert colors == [(1600, (130, 140, 120, 255)), (900, (130, 150, 120, 0))] @@ -572,7 +733,7 @@ def test_with_color_fuzz(self): img = self._make_test_image() img = make_transparent(img, (128, 154, 121), tolerance=5) - assert img.mode == 'RGBA' + assert img.mode == "RGBA" assert img.size == (50, 50) colors = img.getcolors() assert colors == [(1600, (130, 140, 120, 255)), (900, (130, 150, 120, 0))] @@ -580,7 +741,7 @@ def test_no_match(self): img = self._make_test_image() img = make_transparent(img, (130, 160, 120), tolerance=5) - assert img.mode == 'RGBA' + assert img.mode == "RGBA" assert img.size == (50, 50) colors = img.getcolors() assert colors == [(1600, (130, 140, 120, 255)), (900, (130, 150, 120, 255))] @@ -588,10 +749,10 @@ def test_from_paletted(self): img = self._make_test_image().quantize(256) img = make_transparent(img, (130, 150, 120), tolerance=5) - assert img.mode == 'RGBA' + assert img.mode == "RGBA" assert img.size == (50, 50) colors = img.getcolors() - eq_(colors, [(1600, (130, 140, 120, 255)), (900, (130, 150, 120, 0))]) + assert colors == [(1600, (130, 140, 120, 255)), (900, (130, 150, 120, 0))] def test_from_transparent(self): img = self._make_transp_test_image() @@ -599,164 +760,179 @@ draw.rectangle((0, 0, 4, 4), fill=(130, 100, 120, 0)) draw.rectangle((5, 5, 9, 9), fill=(130, 150, 120, 255)) img = make_transparent(img, (130, 150, 120, 120), tolerance=5) - assert img.mode == 'RGBA' + assert img.mode == "RGBA" assert img.size == (50, 50) colors = sorted(img.getcolors(), reverse=True) - eq_(colors, [(1550, (130, 140, 120, 100)), (900, (130, 150, 120, 0)), - (25, (130, 150, 120, 255)), (25, (130, 100, 120, 0))]) + + assert colors == [ + (1550, (130, 140, 120, 100)), + (900, (130, 150, 120, 0)), + (25, (130, 150, 120, 255)), + (25, (130, 100, 120, 0)), + ] class TestTileSplitter(object): + def test_background_larger_crop(self): - img = ImageSource(Image.new('RGB', (356, 266), (130, 140, 120))) - img_opts = ImageOptions('RGB') + img = ImageSource(Image.new("RGB", (356, 266), (130, 140, 120))) + img_opts = ImageOptions("RGB") splitter = TileSplitter(img, img_opts) tile = splitter.get_tile((0, 0), (256, 256)) - eq_(tile.size, (256, 256)) + assert tile.size == (256, 256) colors = tile.as_image().getcolors() - eq_(colors, [(256*256, (130, 140, 120))]) + assert colors == [(256 * 256, (130, 140, 120))] tile = splitter.get_tile((256, 256), (256, 256)) - eq_(tile.size, (256, 256)) + assert tile.size == (256, 256) colors = tile.as_image().getcolors() - eq_(sorted(colors), [(10*100, (130, 140, 120)), (256*256-10*100, (255, 255, 255))]) + assert sorted(colors) == [ + (10 * 100, (130, 140, 120)), + (256 * 256 - 10 * 100, (255, 255, 255)), + ] def test_background_larger_crop_with_transparent(self): - img = ImageSource(Image.new('RGBA', (356, 266), (130, 140, 120, 255))) - img_opts = ImageOptions('RGBA', transparent=True) + img = ImageSource(Image.new("RGBA", (356, 266), (130, 140, 120, 255))) + img_opts = ImageOptions("RGBA", transparent=True) splitter = TileSplitter(img, img_opts) tile = splitter.get_tile((0, 0), (256, 256)) - eq_(tile.size, (256, 256)) + assert tile.size == (256, 256) colors = tile.as_image().getcolors() - eq_(colors, [(256*256, (130, 140, 120, 255))]) + assert colors == [(256 * 256, (130, 140, 120, 255))] tile = splitter.get_tile((256, 256), (256, 256)) - eq_(tile.size, (256, 256)) + assert tile.size == (256, 256) colors = tile.as_image().getcolors() - eq_(sorted(colors), [(10*100, (130, 140, 120, 255)), (256*256-10*100, (255, 255, 255, 0))]) + assert sorted(colors) == [ + (10 * 100, (130, 140, 120, 255)), + (256 * 256 - 10 * 100, (255, 255, 255, 0)), + ] + +@pytest.mark.skipif(not hasattr(Image, "FASTOCTREE"), reason="PIL has no FASTOCTREE") class TestHasTransparency(object): - def test_rgb(self): - if not hasattr(Image, 'FASTOCTREE'): - raise SkipTest() - img = Image.new('RGB', (10, 10)) + def test_rgb(self): + img = Image.new("RGB", (10, 10)) assert not img_has_transparency(img) img = quantize(img, alpha=False) assert not img_has_transparency(img) def test_rbga(self): - if not hasattr(Image, 'FASTOCTREE'): - raise SkipTest() - - img = Image.new('RGBA', (10, 10), (100, 200, 50, 255)) + img = Image.new("RGBA", (10, 10), (100, 200, 50, 255)) img.paste((255, 50, 50, 0), (3, 3, 7, 7)) assert img_has_transparency(img) img = quantize(img, alpha=True) assert img_has_transparency(img) + class TestPeekImageFormat(object): - def test_peek(self): - yield self.check, 'png', 'png' - yield self.check, 'tiff', 'tiff' - yield self.check, 'gif', 'gif' - yield self.check, 'jpeg', 'jpeg' - yield self.check, 'bmp', None - def check(self, format, expected_format): + @pytest.mark.parametrize( + "format,expected_format", + [ + ["png", "png"], + ["tiff", "tiff"], + ["gif", "gif"], + ["jpeg", "jpeg"], + ["bmp", None], + ], + ) + def test_peek_format(self, format, expected_format): buf = BytesIO() - Image.new('RGB', (100, 100)).save(buf, format) - eq_(peek_image_format(buf), expected_format) + Image.new("RGB", (100, 100)).save(buf, format) + assert peek_image_format(buf) == expected_format + class TestBandMerge(object): + def setup(self): - self.img0 = ImageSource(Image.new('RGB', (10, 10), (0, 10, 20))) - self.img1 = ImageSource(Image.new('RGB', (10, 10), (100, 110, 120))) - self.img2 = ImageSource(Image.new('RGB', (10, 10), (200, 210, 220))) - self.img3 = ImageSource(Image.new('RGB', (10, 10), (0, 255, 0))) + self.img0 = ImageSource(Image.new("RGB", (10, 10), (0, 10, 20))) + self.img1 = ImageSource(Image.new("RGB", (10, 10), (100, 110, 120))) + self.img2 = ImageSource(Image.new("RGB", (10, 10), (200, 210, 220))) + self.img3 = ImageSource(Image.new("RGB", (10, 10), (0, 255, 0))) self.blank = BlankImageSource(size=(10, 10), image_opts=ImageOptions()) def test_merge_noops(self): """ Check that black image is returned for no ops. """ - merger = BandMerger(mode='RGB') + merger = BandMerger(mode="RGB") - img_opts = ImageOptions('RGB') + img_opts = ImageOptions("RGB") result = merger.merge([self.img0], img_opts) img = result.as_image() - eq_(img.size, (10, 10)) - eq_(img.getpixel((0, 0)), (0, 0, 0)) + assert img.size == (10, 10) + assert img.getpixel((0, 0)) == (0, 0, 0) def test_merge_missing_source(self): """ Check that empty source list or source list with missing images returns BlankImageSource. """ - merger = BandMerger(mode='RGB') + merger = BandMerger(mode="RGB") merger.add_ops(dst_band=0, src_img=0, src_band=0) merger.add_ops(dst_band=1, src_img=1, src_band=0) merger.add_ops(dst_band=2, src_img=2, src_band=0) - img_opts = ImageOptions('RGBA', transparent=True) + img_opts = ImageOptions("RGBA", transparent=True) result = merger.merge([], img_opts, size=(10, 10)) img = result.as_image() - eq_(img.size, (10, 10)) - eq_(img.getpixel((0, 0)), (255, 255, 255, 0)) + assert img.size == (10, 10) + assert img.getpixel((0, 0)) == (255, 255, 255, 0) result = merger.merge([self.img0, self.img1], img_opts, size=(10, 10)) img = result.as_image() - eq_(img.size, (10, 10)) - eq_(img.getpixel((0, 0)), (255, 255, 255, 0)) - + assert img.size == (10, 10) + assert img.getpixel((0, 0)) == (255, 255, 255, 0) def test_rgb_merge(self): """ Check merge of RGB bands """ - merger = BandMerger(mode='RGB') + merger = BandMerger(mode="RGB") merger.add_ops(dst_band=1, src_img=0, src_band=0, factor=0.5) merger.add_ops(dst_band=1, src_img=3, src_band=1, factor=0.5) merger.add_ops(dst_band=0, src_img=2, src_band=1) merger.add_ops(dst_band=2, src_img=1, src_band=2) - img_opts = ImageOptions('RGB') + img_opts = ImageOptions("RGB") result = merger.merge([self.img0, self.img1, self.img2, self.img3], img_opts) img = result.as_image() - eq_(img.getpixel((0, 0)), (210, 127, 120)) + assert img.getpixel((0, 0)) == (210, 127, 120) def test_rgb_merge_missing(self): """ Check missing band is set to 0 """ - merger = BandMerger(mode='RGB') + merger = BandMerger(mode="RGB") merger.add_ops(dst_band=0, src_img=2, src_band=1) merger.add_ops(dst_band=2, src_img=1, src_band=2) - img_opts = ImageOptions('RGB') + img_opts = ImageOptions("RGB") result = merger.merge([self.img0, self.img1, self.img2, self.img3], img_opts) img = result.as_image() - eq_(img.getpixel((0, 0)), (210, 0, 120)) + assert img.getpixel((0, 0)) == (210, 0, 120) def test_rgba_merge(self): """ Check merge of RGBA bands """ - merger = BandMerger(mode='RGBA') + merger = BandMerger(mode="RGBA") merger.add_ops(dst_band=1, src_img=0, src_band=0, factor=0.5) merger.add_ops(dst_band=1, src_img=3, src_band=1, factor=0.5) @@ -764,106 +940,107 @@ merger.add_ops(dst_band=2, src_img=1, src_band=2) merger.add_ops(dst_band=3, src_img=1, src_band=1) - img_opts = ImageOptions('RGBA') + img_opts = ImageOptions("RGBA") result = merger.merge([self.img0, self.img1, self.img2, self.img3], img_opts) img = result.as_image() - eq_(img.getpixel((0, 0)), (210, 127, 120, 110)) + assert img.getpixel((0, 0)) == (210, 127, 120, 110) def test_rgba_merge_missing_a(self): """ Check that missing alpha band defaults to opaque """ - merger = BandMerger(mode='RGBA') + merger = BandMerger(mode="RGBA") merger.add_ops(dst_band=1, src_img=0, src_band=0, factor=0.5) merger.add_ops(dst_band=1, src_img=3, src_band=1, factor=0.5) merger.add_ops(dst_band=0, src_img=2, src_band=1) merger.add_ops(dst_band=2, src_img=1, src_band=2) - img_opts = ImageOptions('RGBA') + img_opts = ImageOptions("RGBA") result = merger.merge([self.img0, self.img1, self.img2, self.img3], img_opts) img = result.as_image() - eq_(img.getpixel((0, 0)), (210, 127, 120, 255)) + assert img.getpixel((0, 0)) == (210, 127, 120, 255) def test_l_merge(self): """ Check merge bands to grayscale image """ - merger = BandMerger(mode='L') + merger = BandMerger(mode="L") merger.add_ops(dst_band=0, src_img=0, src_band=2, factor=0.2) merger.add_ops(dst_band=0, src_img=2, src_band=1, factor=0.3) merger.add_ops(dst_band=0, src_img=3, src_band=1, factor=0.5) - img_opts = ImageOptions('L') + img_opts = ImageOptions("L") result = merger.merge([self.img0, self.img1, self.img2, self.img3], img_opts) img = result.as_image() - eq_(img.getpixel((0, 0)), int(20*0.2) + int(210*0.3) + int(255*0.5)) + assert img.getpixel((0, 0)) == int(20 * 0.2) + int(210 * 0.3) + int(255 * 0.5) def test_p_merge(self): """ Check merge bands to paletted image """ - merger = BandMerger(mode='RGB') + merger = BandMerger(mode="RGB") merger.add_ops(dst_band=1, src_img=0, src_band=0, factor=0.5) merger.add_ops(dst_band=1, src_img=3, src_band=1, factor=0.5) merger.add_ops(dst_band=0, src_img=2, src_band=1) merger.add_ops(dst_band=2, src_img=1, src_band=2) - img_opts = ImageOptions('P', format='image/png', encoding_options={'quantizer': 'mediancut'}) + img_opts = ImageOptions( + "P", format="image/png", encoding_options={"quantizer": "mediancut"} + ) result = merger.merge([self.img0, self.img1, self.img2, self.img3], img_opts) # need to encode to get conversion to P img = Image.open(result.as_buffer()) - eq_(img.mode, 'P') - img = img.convert('RGB') - eq_(img.getpixel((0, 0)), (210, 127, 120)) + assert img.mode == "P" + img = img.convert("RGB") + assert img.getpixel((0, 0)) == (210, 127, 120) def test_from_p_merge(self): """ Check merge bands from paletted image """ - merger = BandMerger(mode='RGB') + merger = BandMerger(mode="RGB") merger.add_ops(dst_band=0, src_img=0, src_band=2) merger.add_ops(dst_band=1, src_img=0, src_band=1) merger.add_ops(dst_band=2, src_img=0, src_band=0) - img = Image.new('RGB', (10, 10), (0, 100, 200)).quantize(256) - eq_(img.mode, 'P') + img = Image.new("RGB", (10, 10), (0, 100, 200)).quantize(256) + assert img.mode == "P" # src img is P but we can still access RGB bands src_img = ImageSource(img) - img_opts = ImageOptions('RGB') + img_opts = ImageOptions("RGB") result = merger.merge([src_img], img_opts) img = result.as_image() - eq_(img.mode, 'RGB') - eq_(img.getpixel((0, 0)), (200, 100, 0)) + assert img.mode == "RGB" + assert img.getpixel((0, 0)) == (200, 100, 0) def test_from_mixed_merge(self): """ Check merge RGBA bands from image without alpha (mixed) """ - merger = BandMerger(mode='RGBA') + merger = BandMerger(mode="RGBA") merger.add_ops(dst_band=0, src_img=0, src_band=2) merger.add_ops(dst_band=1, src_img=0, src_band=1) merger.add_ops(dst_band=2, src_img=0, src_band=0) merger.add_ops(dst_band=3, src_img=0, src_band=3) - img = Image.new('RGB', (10, 10), (0, 100, 200)) + img = Image.new("RGB", (10, 10), (0, 100, 200)) src_img = ImageSource(img) - img_opts = ImageOptions('RGBA') + img_opts = ImageOptions("RGBA") result = merger.merge([src_img], img_opts) img = result.as_image() - eq_(img.mode, 'RGBA') - eq_(img.getpixel((0, 0)), (200, 100, 0, 255)) - + assert img.mode == "RGBA" + assert img.getpixel((0, 0)) == (200, 100, 0, 255) diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_multiapp.py mapproxy-1.12.0/mapproxy/test/unit/test_multiapp.py --- mapproxy-1.11.0/mapproxy/test/unit/test_multiapp.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_multiapp.py 2019-08-30 07:34:08.000000000 +0000 @@ -16,68 +16,61 @@ import os import time -import tempfile -import shutil + +import pytest + from mapproxy.multiapp import DirectoryConfLoader, MultiMapProxy -from nose.tools import eq_ class TestDirectoryConfLoader(object): - def setup(self): - self.dir = tempfile.mkdtemp() - - def teardown(self): - shutil.rmtree(self.dir) - def make_conf_file(self, name): - conf_file_name = os.path.join(self.dir, name) - with open(conf_file_name, 'wb'): + @pytest.fixture + def loader(self, tmpdir): + return DirectoryConfLoader(tmpdir.strpath) + + def make_conf_file(self, dir, name): + conf_file_name = os.path.join(dir, name) + with open(conf_file_name, "wb"): pass return conf_file_name - def test_available_apps_empty(self): - loader = DirectoryConfLoader(self.dir) - eq_(loader.available_apps(), []) - - def test_available_apps(self): - self.make_conf_file('foo.yaml') - self.make_conf_file('bar.yaml') - loader = DirectoryConfLoader(self.dir) - eq_(set(loader.available_apps()), set(['foo', 'bar'])) - self.make_conf_file('bazz.yaml') - eq_(set(loader.available_apps()), set(['foo', 'bar', 'bazz'])) - - def test_app_available(self): - self.make_conf_file('foo.yaml') - loader = DirectoryConfLoader(self.dir) - assert loader.app_available('foo') - assert not loader.app_available('bar') - - def test_app_conf(self): - foo_conf_file = self.make_conf_file('foo.yaml') - loader = DirectoryConfLoader(self.dir) - app_conf = loader.app_conf('foo') - eq_(app_conf['mapproxy_conf'], foo_conf_file) - - def test_app_conf_unknown_app(self): - loader = DirectoryConfLoader(self.dir) - app_conf = loader.app_conf('foo') + def test_available_apps_empty(self, loader): + assert loader.available_apps() == [] + + def test_available_apps(self, loader): + self.make_conf_file(loader.base_dir, "foo.yaml") + self.make_conf_file(loader.base_dir, "bar.yaml") + assert set(loader.available_apps()) == set(["foo", "bar"]) + self.make_conf_file(loader.base_dir, "bazz.yaml") + assert set(loader.available_apps()) == set(["foo", "bar", "bazz"]) + + def test_app_available(self, loader): + self.make_conf_file(loader.base_dir, "foo.yaml") + assert loader.app_available("foo") + assert not loader.app_available("bar") + + def test_app_conf(self, loader): + foo_conf_file = self.make_conf_file(loader.base_dir, "foo.yaml") + app_conf = loader.app_conf("foo") + assert app_conf["mapproxy_conf"] == foo_conf_file + + def test_app_conf_unknown_app(self, loader): + app_conf = loader.app_conf("foo") assert app_conf is None - def test_needs_reload(self): - foo_conf_file = self.make_conf_file('foo.yaml') + def test_needs_reload(self, loader): + foo_conf_file = self.make_conf_file(loader.base_dir, "foo.yaml") mtime = os.path.getmtime(foo_conf_file) timestamps = {foo_conf_file: mtime} - loader = DirectoryConfLoader(self.dir) - assert loader.needs_reload('foo', timestamps) == False + assert loader.needs_reload("foo", timestamps) == False timestamps[foo_conf_file] -= 10 - assert loader.needs_reload('foo', timestamps) == True + assert loader.needs_reload("foo", timestamps) == True - def test_custom_suffix(self): - self.make_conf_file('foo.conf') - loader = DirectoryConfLoader(self.dir, suffix='.conf') - assert loader.app_available('foo') + def test_custom_suffix(self, loader): + self.make_conf_file(loader.base_dir, "foo.conf") + loader = DirectoryConfLoader(loader.base_dir, suffix=".conf") + assert loader.app_available("foo") minimal_mapproxy_conf = b""" @@ -85,7 +78,7 @@ wms: layers: - mylayer: + - name: mylayer title: My Layer sources: [mysource] @@ -97,75 +90,74 @@ layers: foo,bar """ + class DummyReq(object): - script_url = '' + script_url = "" + class TestMultiMapProxy(object): - def setup(self): - self.dir = tempfile.mkdtemp() - self.loader = DirectoryConfLoader(self.dir) - - def teardown(self): - shutil.rmtree(self.dir) - - def make_conf_file(self, name): - app_conf_file_name = os.path.join(self.dir, name) - with open(app_conf_file_name, 'wb') as f: + + @pytest.fixture + def loader(self, tmpdir): + return DirectoryConfLoader(tmpdir.strpath) + + def make_conf_file(self, dir, name): + app_conf_file_name = os.path.join(dir, name) + with open(app_conf_file_name, "wb") as f: f.write(minimal_mapproxy_conf) return app_conf_file_name - def test_listing_with_apps(self): - self.make_conf_file('foo.yaml') - mmp = MultiMapProxy(self.loader, list_apps=True) + def test_listing_with_apps(self, loader): + self.make_conf_file(loader.base_dir, "foo.yaml") + mmp = MultiMapProxy(loader, list_apps=True) resp = mmp.index_list(DummyReq()) - assert 'foo' in resp.response + assert "foo" in resp.response - def test_listing_without_apps(self): - self.make_conf_file('foo.yaml') - mmp = MultiMapProxy(self.loader) + def test_listing_without_apps(self, loader): + self.make_conf_file(loader.base_dir, "foo.yaml") + mmp = MultiMapProxy(loader) resp = mmp.index_list(DummyReq()) - assert 'foo' not in resp.response - assert mmp.proj_app('foo') is not None + assert "foo" not in resp.response + assert mmp.proj_app("foo") is not None - def test_cached_app_loading(self): - self.make_conf_file('foo.yaml') - mmp = MultiMapProxy(self.loader) - app1 = mmp.proj_app('foo') - app2 = mmp.proj_app('foo') + def test_cached_app_loading(self, loader): + self.make_conf_file(loader.base_dir, "foo.yaml") + mmp = MultiMapProxy(loader) + app1 = mmp.proj_app("foo") + app2 = mmp.proj_app("foo") # app is cached assert app1 is app2 - def test_app_reloading(self): - app_conf_file_name = self.make_conf_file('foo.yaml') - mmp = MultiMapProxy(self.loader) - app = mmp.proj_app('foo') + def test_app_reloading(self, loader): + app_conf_file_name = self.make_conf_file(loader.base_dir, "foo.yaml") + mmp = MultiMapProxy(loader) + app = mmp.proj_app("foo") # touch configuration file - os.utime(app_conf_file_name, (time.time()+10, time.time()+10)) + os.utime(app_conf_file_name, (time.time() + 10, time.time() + 10)) # app was reloaded - assert app is not mmp.proj_app('foo') + assert app is not mmp.proj_app("foo") - def test_app_unloading(self): - self.make_conf_file('app1.yaml') - self.make_conf_file('app2.yaml') - self.make_conf_file('app3.yaml') - mmp = MultiMapProxy(self.loader, app_cache_size=2) + def test_app_unloading(self, loader): + self.make_conf_file(loader.base_dir, "app1.yaml") + self.make_conf_file(loader.base_dir, "app2.yaml") + self.make_conf_file(loader.base_dir, "app3.yaml") + mmp = MultiMapProxy(loader, app_cache_size=2) - app1 = mmp.proj_app('app1') - app2 = mmp.proj_app('app2') + app1 = mmp.proj_app("app1") + app2 = mmp.proj_app("app2") # lru cache [app1, app2] - assert app1 is mmp.proj_app('app1') - assert app2 is mmp.proj_app('app2') + assert app1 is mmp.proj_app("app1") + assert app2 is mmp.proj_app("app2") # lru cache [app1, app2] - app3 = mmp.proj_app('app3') + app3 = mmp.proj_app("app3") # lru cache [app2, app3] - assert app3 is mmp.proj_app('app3') - assert app2 is mmp.proj_app('app2') - assert app1 is not mmp.proj_app('app1') + assert app3 is mmp.proj_app("app3") + assert app2 is mmp.proj_app("app2") + assert app1 is not mmp.proj_app("app1") # lru cache [app2, app1] - assert app3 is not mmp.proj_app('app3') - + assert app3 is not mmp.proj_app("app3") diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_ogr_reader.py mapproxy-1.12.0/mapproxy/test/unit/test_ogr_reader.py --- mapproxy-1.11.0/mapproxy/test/unit/test_ogr_reader.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_ogr_reader.py 2019-08-30 07:34:08.000000000 +0000 @@ -14,28 +14,34 @@ # limitations under the License. import os + +import pytest + from mapproxy.util.ogr import OGRShapeReader, libgdal -from nose.tools import eq_ -from nose.plugins.skip import SkipTest -if not libgdal: - raise SkipTest('libgdal not found') -polygon_file = os.path.join(os.path.dirname(__file__), 'polygons', 'polygons.shp') +polygon_file = os.path.join(os.path.dirname(__file__), "polygons", "polygons.shp") + +@pytest.mark.skipif(not libgdal, reason="libgdal not found") class TestOGRShapeReader(object): - def setup(self): - self.reader = OGRShapeReader(polygon_file) - def test_read_all(self): - wkts = list(self.reader.wkts()) - eq_(len(wkts), 3) + + @pytest.fixture + def reader(self): + return OGRShapeReader(polygon_file) + + def test_read_all(self, reader): + wkts = list(reader.wkts()) + assert len(wkts) == 3 for wkt in wkts: - assert wkt.startswith(b'POLYGON ('), 'unexpected WKT: %s' % wkt - def test_read_filter(self): - wkts = list(self.reader.wkts(where='name = "germany"')) - eq_(len(wkts), 2) + assert wkt.startswith(b"POLYGON ("), "unexpected WKT: %s" % wkt + + def test_read_filter(self, reader): + wkts = list(reader.wkts(where="name = 'germany'")) + assert len(wkts) == 2 for wkt in wkts: - assert wkt.startswith(b'POLYGON ('), 'unexpected WKT: %s' % wkt - def test_read_filter_no_match(self): - wkts = list(self.reader.wkts(where='name = "foo"')) - eq_(len(wkts), 0) + assert wkt.startswith(b"POLYGON ("), "unexpected WKT: %s" % wkt + + def test_read_filter_no_match(self, reader): + wkts = list(reader.wkts(where="name = 'foo'")) + assert len(wkts) == 0 diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_request.py mapproxy-1.12.0/mapproxy/test/unit/test_request.py --- mapproxy-1.11.0/mapproxy/test/unit/test_request.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_request.py 2019-08-30 07:34:08.000000000 +0000 @@ -16,587 +16,730 @@ from __future__ import print_function +import pickle + +import pytest + from mapproxy.srs import SRS from mapproxy.request.base import url_decode, Request, NoCaseMultiDict, RequestParams from mapproxy.request.tile import TMSRequest, tile_request, TileRequest -from mapproxy.request.wms import (wms_request, WMSMapRequest, WMSMapRequestParams, - WMS111MapRequest, WMS100MapRequest, WMS130MapRequest, - WMS111FeatureInfoRequest) +from mapproxy.request.wms import ( + wms_request, + WMSMapRequest, + WMSMapRequestParams, + WMS111MapRequest, + WMS100MapRequest, + WMS130MapRequest, + WMS111FeatureInfoRequest, +) from mapproxy.request.arcgis import ArcGISRequest, ArcGISIdentifyRequest from mapproxy.exception import RequestError -from mapproxy.request.wms.exception import (WMS111ExceptionHandler, WMSImageExceptionHandler, - WMSBlankExceptionHandler) +from mapproxy.request.wms.exception import ( + WMS111ExceptionHandler, + WMSImageExceptionHandler, + WMSBlankExceptionHandler, +) from mapproxy.test.http import make_wsgi_env, assert_url_eq, assert_query_eq -import pickle -from nose.tools import eq_ class TestNoCaseMultiDict(object): + def test_from_iterable(self): - data = (('layers', 'foo,bar'), ('laYERs', 'baz'), ('crs', 'EPSG:4326')) + data = (("layers", "foo,bar"), ("laYERs", "baz"), ("crs", "EPSG:4326")) nc_dict = NoCaseMultiDict(data) print(nc_dict) - for name in ('layers', 'LAYERS', 'lAYeRS'): - assert name in nc_dict, name + ' not found' - assert nc_dict.get_all('layers') == ['foo,bar', 'baz'] - assert nc_dict.get_all('crs') == ['EPSG:4326'] + for name in ("layers", "LAYERS", "lAYeRS"): + assert name in nc_dict, name + " not found" + assert nc_dict.get_all("layers") == ["foo,bar", "baz"] + assert nc_dict.get_all("crs") == ["EPSG:4326"] def test_from_dict(self): - data = [('layers', 'foo,bar'), ('laYERs', 'baz'), ('crs', 'EPSG:4326')] + data = [("layers", "foo,bar"), ("laYERs", "baz"), ("crs", "EPSG:4326")] nc_dict = NoCaseMultiDict(data) print(nc_dict) - for name in ('layers', 'LAYERS', 'lAYeRS'): - assert name in nc_dict, name + ' not found' - assert nc_dict.get_all('layers') == ['foo,bar', 'baz'] - assert nc_dict.get_all('crs') == ['EPSG:4326'] + for name in ("layers", "LAYERS", "lAYeRS"): + assert name in nc_dict, name + " not found" + assert nc_dict.get_all("layers") == ["foo,bar", "baz"] + assert nc_dict.get_all("crs") == ["EPSG:4326"] def test_iteritems(self): - data = [('LAYERS', 'foo,bar'), ('laYERs', 'baz'), ('crs', 'EPSG:4326')] + data = [("LAYERS", "foo,bar"), ("laYERs", "baz"), ("crs", "EPSG:4326")] nc_dict = NoCaseMultiDict(data) for key, values in nc_dict.iteritems(): - if key in ('LAYERS', 'laYERs'): - assert values == ['foo,bar', 'baz'] - elif key == 'crs': - assert values == ['EPSG:4326'] + if key in ("LAYERS", "laYERs"): + assert values == ["foo,bar", "baz"] + elif key == "crs": + assert values == ["EPSG:4326"] else: - assert False, 'unexpected key ' + key + assert False, "unexpected key " + key def test_multiple_sets(self): nc_dict = NoCaseMultiDict() - nc_dict['foo'] = 'bar' - assert nc_dict['FOO'] == 'bar' - nc_dict['foo'] = 'baz' - assert nc_dict['FOO'] == 'baz' + nc_dict["foo"] = "bar" + assert nc_dict["FOO"] == "bar" + nc_dict["foo"] = "baz" + assert nc_dict["FOO"] == "baz" def test_missing_key(self): - nc_dict = NoCaseMultiDict([('foo', 'bar')]) + nc_dict = NoCaseMultiDict([("foo", "bar")]) try: - nc_dict['bar'] - assert False, 'Did not throw KeyError exception.' + nc_dict["bar"] + assert False, "Did not throw KeyError exception." except KeyError: pass def test_get(self): - nc_dict = NoCaseMultiDict([('foo', 'bar'), ('num', '42')]) - assert nc_dict.get('bar') == None - assert nc_dict.get('bar', 'default_bar') == 'default_bar' - assert nc_dict.get('num') == '42' - assert nc_dict.get('num', type_func=int) == 42 - assert nc_dict.get('foo') == 'bar' + nc_dict = NoCaseMultiDict([("foo", "bar"), ("num", "42")]) + assert nc_dict.get("bar") == None + assert nc_dict.get("bar", "default_bar") == "default_bar" + assert nc_dict.get("num") == "42" + assert nc_dict.get("num", type_func=int) == 42 + assert nc_dict.get("foo") == "bar" def test_get_all(self): - nc_dict = NoCaseMultiDict([('foo', 'bar'), ('num', '42'), ('foo', 'biz')]) - assert nc_dict.get_all('bar') == [] - assert nc_dict.get_all('foo') == ['bar', 'biz'] - assert nc_dict.get_all('num') == ['42'] + nc_dict = NoCaseMultiDict([("foo", "bar"), ("num", "42"), ("foo", "biz")]) + assert nc_dict.get_all("bar") == [] + assert nc_dict.get_all("foo") == ["bar", "biz"] + assert nc_dict.get_all("num") == ["42"] def test_set(self): nc_dict = NoCaseMultiDict() - nc_dict.set('foo', 'bar') - assert nc_dict.get_all('fOO') == ['bar'] - nc_dict.set('fOo', 'buzz', append=True) - assert nc_dict.get_all('FOO') == ['bar', 'buzz'] - nc_dict.set('foO', 'bizz') - assert nc_dict.get_all('FOO') == ['bizz'] - nc_dict.set('foO', ['ham', 'spam'], unpack=True) - assert nc_dict.get_all('FOO') == ['ham', 'spam'] - nc_dict.set('FoO', ['egg', 'bacon'], append=True, unpack=True) - assert nc_dict.get_all('FOo') == ['ham', 'spam', 'egg', 'bacon'] + nc_dict.set("foo", "bar") + assert nc_dict.get_all("fOO") == ["bar"] + nc_dict.set("fOo", "buzz", append=True) + assert nc_dict.get_all("FOO") == ["bar", "buzz"] + nc_dict.set("foO", "bizz") + assert nc_dict.get_all("FOO") == ["bizz"] + nc_dict.set("foO", ["ham", "spam"], unpack=True) + assert nc_dict.get_all("FOO") == ["ham", "spam"] + nc_dict.set("FoO", ["egg", "bacon"], append=True, unpack=True) + assert nc_dict.get_all("FOo") == ["ham", "spam", "egg", "bacon"] def test_setitem(self): nc_dict = NoCaseMultiDict() - nc_dict['foo'] = 'bar' - assert nc_dict['foo'] == 'bar' - nc_dict['foo'] = 'buz' - assert nc_dict['foo'] == 'buz' - nc_dict['bar'] = nc_dict['foo'] - assert nc_dict['bar'] == 'buz' - - nc_dict['bing'] = '1' - nc_dict['bong'] = '2' - nc_dict['bing'] = nc_dict['bong'] - assert nc_dict['bing'] == '2' - assert nc_dict['bong'] == '2' + nc_dict["foo"] = "bar" + assert nc_dict["foo"] == "bar" + nc_dict["foo"] = "buz" + assert nc_dict["foo"] == "buz" + nc_dict["bar"] = nc_dict["foo"] + assert nc_dict["bar"] == "buz" + + nc_dict["bing"] = "1" + nc_dict["bong"] = "2" + nc_dict["bing"] = nc_dict["bong"] + assert nc_dict["bing"] == "2" + assert nc_dict["bong"] == "2" def test_del(self): - nc_dict = NoCaseMultiDict([('foo', 'bar'), ('num', '42')]) - assert nc_dict['fOO'] == 'bar' - del nc_dict['FOO'] - assert nc_dict.get('foo') == None + nc_dict = NoCaseMultiDict([("foo", "bar"), ("num", "42")]) + assert nc_dict["fOO"] == "bar" + del nc_dict["FOO"] + assert nc_dict.get("foo") == None class DummyRequest(object): - def __init__(self, args, url=''): + + def __init__(self, args, url=""): self.args = args self.base_url = url + class TestWMSMapRequest(object): + def setup(self): - self.base_req = url_decode('''SERVICE=WMS&format=image%2Fpng&layers=foo&styles=& + self.base_req = url_decode( + """SERVICE=WMS&format=image%2Fpng&layers=foo&styles=& REQUEST=GetMap&height=300&srs=EPSG%3A4326&VERSION=1.1.1& -bbox=7,50,8,51&width=400'''.replace('\n','')) +bbox=7,50,8,51&width=400""".replace( + "\n", "" + ) + ) + class TestWMS100MapRequest(TestWMSMapRequest): + def setup(self): TestWMSMapRequest.setup(self) - del self.base_req['service'] - del self.base_req['version'] - self.base_req['wmtver'] = '1.0.0' - self.base_req['request'] = 'Map' + del self.base_req["service"] + del self.base_req["version"] + self.base_req["wmtver"] = "1.0.0" + self.base_req["request"] = "Map" def test_basic_request(self): req = wms_request(DummyRequest(self.base_req), validate=False) assert isinstance(req, WMS100MapRequest) - eq_(req.params.request, 'GetMap') + assert req.params.request == "GetMap" + class TestWMS111MapRequest(TestWMSMapRequest): + def test_basic_request(self): req = wms_request(DummyRequest(self.base_req), validate=False) assert isinstance(req, WMS111MapRequest) - eq_(req.params.request, 'GetMap') + assert req.params.request == "GetMap" + class TestWMS130MapRequest(TestWMSMapRequest): + def setup(self): TestWMSMapRequest.setup(self) - self.base_req['version'] = '1.3.0' - self.base_req['crs'] = self.base_req['srs'] - del self.base_req['srs'] + self.base_req["version"] = "1.3.0" + self.base_req["crs"] = self.base_req["srs"] + del self.base_req["srs"] def test_basic_request(self): req = wms_request(DummyRequest(self.base_req), validate=False) assert isinstance(req, WMS130MapRequest) - eq_(req.params.request, 'GetMap') - eq_(req.params.bbox, (50.0, 7.0, 51.0, 8.0)) + assert req.params.request == "GetMap" + assert req.params.bbox == (50.0, 7.0, 51.0, 8.0) def test_copy_with_request_params(self): # check that we allways have our internal axis order - req1 = WMS130MapRequest(param=dict(bbox="10,0,20,40", crs='EPSG:4326')) - eq_(req1.params.bbox, (0.0, 10.0, 40.0, 20.0)) - req2 = WMS111MapRequest(param=dict(bbox="0,10,40,20", srs='EPSG:4326')) - eq_(req2.params.bbox, (0.0, 10.0, 40.0, 20.0)) + req1 = WMS130MapRequest(param=dict(bbox="10,0,20,40", crs="EPSG:4326")) + assert req1.params.bbox == (0.0, 10.0, 40.0, 20.0) + req2 = WMS111MapRequest(param=dict(bbox="0,10,40,20", srs="EPSG:4326")) + assert req2.params.bbox == (0.0, 10.0, 40.0, 20.0) # 130 <- 111 req3 = req1.copy_with_request_params(req2) - eq_(req3.params.bbox, (0.0, 10.0, 40.0, 20.0)) + assert req3.params.bbox == (0.0, 10.0, 40.0, 20.0) assert isinstance(req3, WMS130MapRequest) # 130 <- 130 req4 = req1.copy_with_request_params(req3) - eq_(req4.params.bbox, (0.0, 10.0, 40.0, 20.0)) + assert req4.params.bbox == (0.0, 10.0, 40.0, 20.0) assert isinstance(req4, WMS130MapRequest) # 111 <- 130 req5 = req2.copy_with_request_params(req3) - eq_(req5.params.bbox, (0.0, 10.0, 40.0, 20.0)) + assert req5.params.bbox == (0.0, 10.0, 40.0, 20.0) assert isinstance(req5, WMS111MapRequest) class TestWMS111FeatureInfoRequest(TestWMSMapRequest): + def setup(self): TestWMSMapRequest.setup(self) - self.base_req['request'] = 'GetFeatureInfo' - self.base_req['x'] = '100' - self.base_req['y'] = '150' - self.base_req['query_layers'] = 'foo' + self.base_req["request"] = "GetFeatureInfo" + self.base_req["x"] = "100" + self.base_req["y"] = "150" + self.base_req["query_layers"] = "foo" def test_basic_request(self): - req = wms_request(DummyRequest(self.base_req))#, validate=False) + req = wms_request(DummyRequest(self.base_req)) # , validate=False) assert isinstance(req, WMS111FeatureInfoRequest) def test_pos(self): req = wms_request(DummyRequest(self.base_req)) - eq_(req.params.pos, (100, 150)) + assert req.params.pos == (100, 150) def test_pos_coords(self): req = wms_request(DummyRequest(self.base_req)) - eq_(req.params.pos_coords, (7.25, 50.5)) + assert req.params.pos_coords == (7.25, 50.5) class TestArcGISRequest(object): + def test_base_request(self): req = ArcGISRequest(url="http://example.com/ArcGIS/rest/MapServer/") - eq_("http://example.com/ArcGIS/rest/MapServer/export", req.url) + assert "http://example.com/ArcGIS/rest/MapServer/export" == req.url req.params.bbox = [-180.0, -90.0, 180.0, 90.0] - eq_((-180.0, -90.0, 180.0, 90.0), req.params.bbox) - eq_("-180.0,-90.0,180.0,90.0", req.params["bbox"]) + assert (-180.0, -90.0, 180.0, 90.0) == req.params.bbox + assert "-180.0,-90.0,180.0,90.0" == req.params["bbox"] req.params.size = [256, 256] - eq_((256, 256), req.params.size) - eq_("256,256", req.params["size"]) + assert (256, 256) == req.params.size + assert "256,256" == req.params["size"] req.params.imageSR = "EPSG:4326" - eq_("4326", req.params.imageSR) - eq_("4326", req.params["imageSR"]) + assert "4326" == req.params.imageSR + assert "4326" == req.params["imageSR"] req.params.bboxSR = SRS("EPSG:4326") - eq_("4326", req.params.bboxSR) - eq_("4326", req.params["bboxSR"]) + assert "4326" == req.params.bboxSR + assert "4326" == req.params["bboxSR"] - def check_endpoint(self, url, expected): + @pytest.mark.parametrize( + "url,expected", + [ + [ + "http://example.com/ArcGIS/rest/MapServer/", + "http://example.com/ArcGIS/rest/MapServer/export", + ], + [ + "http://example.com/ArcGIS/rest/MapServer", + "http://example.com/ArcGIS/rest/MapServer/export", + ], + [ + "http://example.com/ArcGIS/rest/MapServer/export", + "http://example.com/ArcGIS/rest/MapServer/export", + ], + [ + "http://example.com/ArcGIS/rest/ImageServer/", + "http://example.com/ArcGIS/rest/ImageServer/exportImage", + ], + [ + "http://example.com/ArcGIS/rest/ImageServer", + "http://example.com/ArcGIS/rest/ImageServer/exportImage", + ], + [ + "http://example.com/ArcGIS/rest/ImageServer/export", + "http://example.com/ArcGIS/rest/ImageServer/exportImage", + ], + [ + "http://example.com/ArcGIS/rest/ImageServer/exportImage", + "http://example.com/ArcGIS/rest/ImageServer/exportImage", + ], + [ + "http://example.com/ArcGIS/rest/MapServer/export?param=foo", + "http://example.com/ArcGIS/rest/MapServer/export?param=foo", + ], + [ + "http://example.com/ArcGIS/rest/ImageServer/export?param=foo", + "http://example.com/ArcGIS/rest/ImageServer/exportImage?param=foo", + ], + ], + ) + def test_endpoint_urls(self, url, expected): req = ArcGISRequest(url=url) - eq_(req.url, expected) - - def test_endpoint_urls(self): - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/MapServer/', 'http://example.com/ArcGIS/rest/MapServer/export' - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/MapServer', 'http://example.com/ArcGIS/rest/MapServer/export' - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/MapServer/export', 'http://example.com/ArcGIS/rest/MapServer/export' - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/ImageServer/', 'http://example.com/ArcGIS/rest/ImageServer/exportImage' - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/ImageServer', 'http://example.com/ArcGIS/rest/ImageServer/exportImage' - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/ImageServer/export', 'http://example.com/ArcGIS/rest/ImageServer/exportImage' - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/ImageServer/exportImage', 'http://example.com/ArcGIS/rest/ImageServer/exportImage' - - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/MapServer/export?param=foo', 'http://example.com/ArcGIS/rest/MapServer/export?param=foo' - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/ImageServer/export?param=foo', 'http://example.com/ArcGIS/rest/ImageServer/exportImage?param=foo' + assert req.url == expected class TestArcGISIndentifyRequest(object): + def test_base_request(self): req = ArcGISIdentifyRequest(url="http://example.com/ArcGIS/rest/MapServer/") - eq_("http://example.com/ArcGIS/rest/MapServer/identify", req.url) + assert "http://example.com/ArcGIS/rest/MapServer/identify" == req.url req.params.bbox = [-180.0, -90.0, 180.0, 90.0] - eq_((-180.0, -90.0, 180.0, 90.0), req.params.bbox) - eq_("-180.0,-90.0,180.0,90.0", req.params["mapExtent"]) + assert (-180.0, -90.0, 180.0, 90.0) == req.params.bbox + assert "-180.0,-90.0,180.0,90.0" == req.params["mapExtent"] req.params.size = [256, 256] - eq_((256, 256), req.params.size) - eq_("256,256,96", req.params["imageDisplay"]) + assert (256, 256) == req.params.size + assert "256,256,96" == req.params["imageDisplay"] req.params.srs = "EPSG:4326" - eq_("EPSG:4326", req.params.srs) - eq_("4326", req.params["sr"]) + assert "EPSG:4326" == req.params.srs + assert "4326" == req.params["sr"] - def check_endpoint(self, url, expected): + @pytest.mark.parametrize( + "url,expected", + [ + [ + "http://example.com/ArcGIS/rest/MapServer/", + "http://example.com/ArcGIS/rest/MapServer/identify", + ], + [ + "http://example.com/ArcGIS/rest/MapServer", + "http://example.com/ArcGIS/rest/MapServer/identify", + ], + [ + "http://example.com/ArcGIS/rest/MapServer/export", + "http://example.com/ArcGIS/rest/MapServer/identify", + ], + [ + "http://example.com/ArcGIS/rest/ImageServer/", + "http://example.com/ArcGIS/rest/ImageServer/identify", + ], + [ + "http://example.com/ArcGIS/rest/ImageServer", + "http://example.com/ArcGIS/rest/ImageServer/identify", + ], + [ + "http://example.com/ArcGIS/rest/ImageServer/export", + "http://example.com/ArcGIS/rest/ImageServer/identify", + ], + [ + "http://example.com/ArcGIS/rest/ImageServer/exportImage", + "http://example.com/ArcGIS/rest/ImageServer/identify", + ], + [ + "http://example.com/ArcGIS/rest/MapServer/export?param=foo", + "http://example.com/ArcGIS/rest/MapServer/identify?param=foo", + ], + [ + "http://example.com/ArcGIS/rest/ImageServer/export?param=foo", + "http://example.com/ArcGIS/rest/ImageServer/identify?param=foo", + ], + ], + ) + def test_endpoint_urls(self, url, expected): req = ArcGISIdentifyRequest(url=url) - eq_(req.url, expected) - - def test_endpoint_urls(self): - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/MapServer/', 'http://example.com/ArcGIS/rest/MapServer/identify' - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/MapServer', 'http://example.com/ArcGIS/rest/MapServer/identify' - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/MapServer/export', 'http://example.com/ArcGIS/rest/MapServer/identify' - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/ImageServer/', 'http://example.com/ArcGIS/rest/ImageServer/identify' - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/ImageServer', 'http://example.com/ArcGIS/rest/ImageServer/identify' - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/ImageServer/export', 'http://example.com/ArcGIS/rest/ImageServer/identify' - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/ImageServer/exportImage', 'http://example.com/ArcGIS/rest/ImageServer/identify' - - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/MapServer/export?param=foo', 'http://example.com/ArcGIS/rest/MapServer/identify?param=foo' - yield self.check_endpoint, 'http://example.com/ArcGIS/rest/ImageServer/export?param=foo', 'http://example.com/ArcGIS/rest/ImageServer/identify?param=foo' + assert req.url == expected class TestRequest(object): + def setup(self): self.env = { - 'HTTP_HOST': 'localhost:5050', - 'PATH_INFO': '/service', - 'QUERY_STRING': 'LAYERS=osm_mapnik&FORMAT=image%2Fpng&SPHERICALMERCATOR=true&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&EXCEPTIONS=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A900913&bbox=1013566.9382067363,7051939.297837454,1030918.1436243634,7069577.142111099&WIDTH=908&HEIGHT=923', - 'REMOTE_ADDR': '127.0.0.1', - 'REQUEST_METHOD': 'GET', - 'SCRIPT_NAME': '', - 'SERVER_NAME': '127.0.0.1', - 'SERVER_PORT': '5050', - 'SERVER_PROTOCOL': 'HTTP/1.1', - 'wsgi.url_scheme': 'http', - } + "HTTP_HOST": "localhost:5050", + "PATH_INFO": "/service", + "QUERY_STRING": "LAYERS=osm_mapnik&FORMAT=image%2Fpng&SPHERICALMERCATOR=true&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&EXCEPTIONS=application%2Fvnd.ogc.se_inimage&SRS=EPSG%3A900913&bbox=1013566.9382067363,7051939.297837454,1030918.1436243634,7069577.142111099&WIDTH=908&HEIGHT=923", + "REMOTE_ADDR": "127.0.0.1", + "REQUEST_METHOD": "GET", + "SCRIPT_NAME": "", + "SERVER_NAME": "127.0.0.1", + "SERVER_PORT": "5050", + "SERVER_PROTOCOL": "HTTP/1.1", + "wsgi.url_scheme": "http", + } + def test_path(self): req = Request(self.env) - assert req.path == '/service' + assert req.path == "/service" def test_host_url(self): req = Request(self.env) - assert req.host_url == 'http://localhost:5050/' + assert req.host_url == "http://localhost:5050/" def test_base_url(self): req = Request(self.env) - assert req.base_url == 'http://localhost:5050/service' + assert req.base_url == "http://localhost:5050/service" - del self.env['HTTP_HOST'] + del self.env["HTTP_HOST"] req = Request(self.env) - assert req.base_url == 'http://127.0.0.1:5050/service' + assert req.base_url == "http://127.0.0.1:5050/service" - self.env['SERVER_PORT'] = '80' + self.env["SERVER_PORT"] = "80" req = Request(self.env) - assert req.base_url == 'http://127.0.0.1/service' + assert req.base_url == "http://127.0.0.1/service" def test_query_string(self): - self.env['QUERY_STRING'] = 'Foo=boo&baz=baa&fOO=bizz' + self.env["QUERY_STRING"] = "Foo=boo&baz=baa&fOO=bizz" req = Request(self.env) - print(req.args['foo']) - assert req.args.get_all('foo') == ['boo', 'bizz'] + print(req.args["foo"]) + assert req.args.get_all("foo") == ["boo", "bizz"] def test_query_string_encoding(self): - env = { - 'QUERY_STRING': 'foo=some%20special%20chars%20%26%20%3D' - } + env = {"QUERY_STRING": "foo=some%20special%20chars%20%26%20%3D"} req = Request(env) - print(req.args['foo']) - assert req.args['foo'] == u'some special chars & =' + print(req.args["foo"]) + assert req.args["foo"] == u"some special chars & =" def test_script_url(self): req = Request(self.env) - eq_(req.script_url, 'http://localhost:5050') - self.env['SCRIPT_NAME'] = '/' + assert req.script_url == "http://localhost:5050" + self.env["SCRIPT_NAME"] = "/" req = Request(self.env) - eq_(req.script_url, 'http://localhost:5050') + assert req.script_url == "http://localhost:5050" - self.env['SCRIPT_NAME'] = '/proxy' + self.env["SCRIPT_NAME"] = "/proxy" req = Request(self.env) - eq_(req.script_url, 'http://localhost:5050/proxy') + assert req.script_url == "http://localhost:5050/proxy" - self.env['SCRIPT_NAME'] = '/proxy/' + self.env["SCRIPT_NAME"] = "/proxy/" req = Request(self.env) - eq_(req.script_url, 'http://localhost:5050/proxy') + assert req.script_url == "http://localhost:5050/proxy" def test_pop_path(self): - self.env['PATH_INFO'] = '/foo/service' + self.env["PATH_INFO"] = "/foo/service" req = Request(self.env) part = req.pop_path() - eq_(part, 'foo') - eq_(self.env['PATH_INFO'], '/service') - eq_(self.env['SCRIPT_NAME'], '/foo') + assert part == "foo" + assert self.env["PATH_INFO"] == "/service" + assert self.env["SCRIPT_NAME"] == "/foo" part = req.pop_path() - eq_(part, 'service') - eq_(self.env['PATH_INFO'], '') - eq_(self.env['SCRIPT_NAME'], '/foo/service') + assert part == "service" + assert self.env["PATH_INFO"] == "" + assert self.env["SCRIPT_NAME"] == "/foo/service" part = req.pop_path() - eq_(part, '') - eq_(self.env['PATH_INFO'], '') - eq_(self.env['SCRIPT_NAME'], '/foo/service') + assert part == "" + assert self.env["PATH_INFO"] == "" + assert self.env["SCRIPT_NAME"] == "/foo/service" def test_maprequest_from_request(): env = { - 'QUERY_STRING': 'layers=bar&bBOx=-90,-80,70.0,+80&format=image/png&'\ - 'WIdth=100&heIGHT=200&LAyerS=foo' + "QUERY_STRING": "layers=bar&bBOx=-90,-80,70.0,+80&format=image/png&" + "WIdth=100&heIGHT=200&LAyerS=foo" } req = WMSMapRequest(param=Request(env).args) assert req.params.bbox == (-90.0, -80.0, 70.0, 80.0) - assert req.params.layers == ['bar', 'foo'] + assert req.params.layers == ["bar", "foo"] assert req.params.size == (100, 200) + class TestWMSMapRequestParams(object): + def setup(self): - self.m = WMSMapRequestParams(url_decode('layers=bar&bBOx=-90,-80,70.0, 80&format=image/png' - '&WIdth=100&heIGHT=200&LAyerS=foo&srs=EPSG%3A0815')) + self.m = WMSMapRequestParams( + url_decode( + "layers=bar&bBOx=-90,-80,70.0, 80&format=image/png" + "&WIdth=100&heIGHT=200&LAyerS=foo&srs=EPSG%3A0815" + ) + ) + def test_empty(self): m = WMSMapRequestParams() - assert m.query_string == '' + assert m.query_string == "" + def test_size(self): assert self.m.size == (100, 200) self.m.size = (250, 350) assert self.m.size == (250, 350) - assert self.m['width'] == '250' - assert self.m['height'] == '350' - del self.m['width'] + assert self.m["width"] == "250" + assert self.m["height"] == "350" + del self.m["width"] assert self.m.size == None + def test_format(self): - assert self.m.format == 'png' - assert self.m.format_mime_type == 'image/png' - self.m['transparent'] = 'True' - assert self.m.format == 'png' + assert self.m.format == "png" + assert self.m.format_mime_type == "image/png" + self.m["transparent"] = "True" + assert self.m.format == "png" + def test_bbox(self): assert self.m.bbox == (-90.0, -80.0, 70.0, 80.0) - del self.m['bbox'] + del self.m["bbox"] assert self.m.bbox is None self.m.bbox = (-90.0, -80.0, 70.0, 80.0) assert self.m.bbox == (-90.0, -80.0, 70.0, 80.0) - self.m.bbox = '0.0, -40.0, 70.0, 80.0' + self.m.bbox = "0.0, -40.0, 70.0, 80.0" assert self.m.bbox == (0.0, -40.0, 70.0, 80.0) self.m.bbox = None assert self.m.bbox is None + def test_transparent(self): assert self.m.transparent == False - self.m['transparent'] = 'trUe' + self.m["transparent"] = "trUe" assert self.m.transparent == True + def test_transparent_bool(self): - self.m['transparent'] = True - assert self.m['transparent'] == 'True' + self.m["transparent"] = True + assert self.m["transparent"] == "True" + def test_bgcolor(self): - assert self.m.bgcolor == '#ffffff' - self.m['bgcolor'] = '0x42cafe' - assert self.m.bgcolor == '#42cafe' + assert self.m.bgcolor == "#ffffff" + self.m["bgcolor"] = "0x42cafe" + assert self.m.bgcolor == "#42cafe" + def test_srs(self): print(self.m.srs) - assert self.m.srs == 'EPSG:0815' - del self.m['srs'] + assert self.m.srs == "EPSG:0815" + del self.m["srs"] assert self.m.srs is None - self.m.srs = SRS('EPSG:4326') - assert self.m.srs == 'EPSG:4326' + self.m.srs = SRS("EPSG:4326") + assert self.m.srs == "EPSG:4326" + def test_layers(self): - assert list(self.m.layers) == ['bar', 'foo'] + assert list(self.m.layers) == ["bar", "foo"] + def test_query_string(self): - assert_query_eq(self.m.query_string, - 'layers=bar,foo&WIdth=100&bBOx=-90,-80,70.0,+80' - '&format=image%2Fpng&srs=EPSG%3A0815&heIGHT=200') + assert_query_eq( + self.m.query_string, + "layers=bar,foo&WIdth=100&bBOx=-90,-80,70.0,+80" + "&format=image%2Fpng&srs=EPSG%3A0815&heIGHT=200", + ) + def test_query_string_encoding(self): m = WMSMapRequestParams() m.layers = ["layer with whitespace", u"layer with ümlauts"] - eq_(m.query_string, 'layers=layer%20with%20whitespace,layer%20with%20%C3%BCmlauts') + assert ( + m.query_string + == "layers=layer%20with%20whitespace,layer%20with%20%C3%BCmlauts" + ) + def test_get(self): - assert self.m.get('LAYERS') == 'bar' - assert self.m.get('width', type_func=int) == 100 + assert self.m.get("LAYERS") == "bar" + assert self.m.get("width", type_func=int) == 100 + def test_set(self): - self.m.set('Layers', 'baz', append=True) - assert self.m.get('LAYERS') == 'bar' - self.m.set('Layers', 'baz') - assert self.m.get('LAYERS') == 'baz' + self.m.set("Layers", "baz", append=True) + assert self.m.get("LAYERS") == "bar" + self.m.set("Layers", "baz") + assert self.m.get("LAYERS") == "baz" + def test_attr_access(self): - assert self.m['width'] == '100' - assert self.m['height'] == '200' + assert self.m["width"] == "100" + assert self.m["height"] == "200" try: self.m.invalid except AttributeError: pass else: assert False + def test_with_defaults(self): - orig_req = WMSMapRequestParams(param=dict(layers='baz')) + orig_req = WMSMapRequestParams(param=dict(layers="baz")) new_req = self.m.with_defaults(orig_req) assert new_req is not self.m - assert self.m.get('LayErs') == 'bar' - assert new_req.get('LAyers') == 'baz' + assert self.m.get("LayErs") == "bar" + assert new_req.get("LAyers") == "baz" assert new_req.size == (100, 200) + class TestURLDecode(object): + def test_key_decode(self): - d = url_decode('white+space=in+key&foo=bar', decode_keys=True) - assert d['white space'] == 'in key' - assert d['foo'] == 'bar' + d = url_decode("white+space=in+key&foo=bar", decode_keys=True) + assert d["white space"] == "in key" + assert d["foo"] == "bar" + def test_include_empty(self): - d = url_decode('bar&foo=baz&bing', include_empty=True) - assert d['bar'] == '' - assert d['foo'] == 'baz' - assert d['bing'] == '' + d = url_decode("bar&foo=baz&bing", include_empty=True) + assert d["bar"] == "" + assert d["foo"] == "baz" + assert d["bing"] == "" def test_non_mime_format(): - m = WMSMapRequest(param={'format': 'jpeg'}) - assert m.params.format == 'jpeg' + m = WMSMapRequest(param={"format": "jpeg"}) + assert m.params.format == "jpeg" + def test_request_w_url(): - url = WMSMapRequest(url='http://localhost:8000/service?', param={'layers': 'foo,bar'}).complete_url - assert_url_eq(url, 'http://localhost:8000/service?layers=foo,bar&styles=&request=GetMap&service=WMS') - url = WMSMapRequest(url='http://localhost:8000/service', param={'layers': 'foo,bar'}).complete_url - assert_url_eq(url, 'http://localhost:8000/service?layers=foo,bar&styles=&request=GetMap&service=WMS') - url = WMSMapRequest(url='http://localhost:8000/service?map=foo', param={'layers': 'foo,bar'}).complete_url - assert_url_eq(url, 'http://localhost:8000/service?map=foo&layers=foo,bar&styles=&request=GetMap&service=WMS') + url = WMSMapRequest( + url="http://localhost:8000/service?", param={"layers": "foo,bar"} + ).complete_url + assert_url_eq( + url, + "http://localhost:8000/service?layers=foo,bar&styles=&request=GetMap&service=WMS", + ) + url = WMSMapRequest( + url="http://localhost:8000/service", param={"layers": "foo,bar"} + ).complete_url + assert_url_eq( + url, + "http://localhost:8000/service?layers=foo,bar&styles=&request=GetMap&service=WMS", + ) + url = WMSMapRequest( + url="http://localhost:8000/service?map=foo", param={"layers": "foo,bar"} + ).complete_url + assert_url_eq( + url, + "http://localhost:8000/service?map=foo&layers=foo,bar&styles=&request=GetMap&service=WMS", + ) + class TestWMSRequest(object): - env = make_wsgi_env("""LAYERS=foo&FORMAT=image%2Fjpeg&SERVICE=WMS&VERSION=1.1.1& + env = make_wsgi_env( + """LAYERS=foo&FORMAT=image%2Fjpeg&SERVICE=WMS&VERSION=1.1.1& REQUEST=GetMap&STYLES=&EXCEPTIONS=application%2Fvnd.ogc.se_xml&SRS=EPSG%3A900913& -BBOX=8,4,9,5&WIDTH=984&HEIGHT=708""".replace('\n', '')) +BBOX=8,4,9,5&WIDTH=984&HEIGHT=708""".replace( + "\n", "" + ) + ) + def setup(self): self.req = Request(self.env) + def test_valid_request(self): map_req = wms_request(self.req) # constructor validates assert map_req.params.size == (984, 708) + def test_invalid_request(self): - del self.req.args['request'] + del self.req.args["request"] try: wms_request(self.req) except RequestError as e: - assert 'request' in e.msg + assert "request" in e.msg else: - assert False, 'RequestError expected' + assert False, "RequestError expected" + def test_exception_handler(self): map_req = wms_request(self.req) assert isinstance(map_req.exception_handler, WMS111ExceptionHandler) + def test_image_exception_handler(self): - self.req.args['exceptions'] = 'application/vnd.ogc.se_inimage' + self.req.args["exceptions"] = "application/vnd.ogc.se_inimage" map_req = wms_request(self.req) assert isinstance(map_req.exception_handler, WMSImageExceptionHandler) + def test_blank_exception_handler(self): - self.req.args['exceptions'] = 'blank' + self.req.args["exceptions"] = "blank" map_req = wms_request(self.req) assert isinstance(map_req.exception_handler, WMSBlankExceptionHandler) + class TestSRSAxisOrder(object): + def setup(self): - params111 = url_decode("""LAYERS=foo&FORMAT=image%2Fjpeg&SERVICE=WMS& + params111 = url_decode( + """LAYERS=foo&FORMAT=image%2Fjpeg&SERVICE=WMS& VERSION=1.1.1&REQUEST=GetMap&STYLES=&EXCEPTIONS=application%2Fvnd.ogc.se_xml& -SRS=EPSG%3A4326&BBOX=8,4,9,5&WIDTH=984&HEIGHT=708""".replace('\n', '')) +SRS=EPSG%3A4326&BBOX=8,4,9,5&WIDTH=984&HEIGHT=708""".replace( + "\n", "" + ) + ) self.req111 = WMS111MapRequest(params111) self.params130 = params111.copy() - self.params130['version'] = '1.3.0' - self.params130['crs'] = self.params130['srs'] - del self.params130['srs'] + self.params130["version"] = "1.3.0" + self.params130["crs"] = self.params130["srs"] + del self.params130["srs"] + def test_111_order(self): - eq_(self.req111.params.bbox, (8, 4, 9, 5)) + assert self.req111.params.bbox == (8, 4, 9, 5) + def test_130_order_geog(self): req130 = WMS130MapRequest(self.params130) - eq_(req130.params.bbox, (4, 8, 5, 9)) - self.params130['crs'] = 'EPSG:4258' + assert req130.params.bbox == (4, 8, 5, 9) + self.params130["crs"] = "EPSG:4258" req130 = WMS130MapRequest(self.params130) - eq_(req130.params.bbox, (4, 8, 5, 9)) + assert req130.params.bbox == (4, 8, 5, 9) + def test_130_order_geog_old(self): - self.params130['crs'] = 'CRS:84' + self.params130["crs"] = "CRS:84" req130 = WMS130MapRequest(self.params130) - eq_(req130.params.bbox, (8, 4, 9, 5)) + assert req130.params.bbox == (8, 4, 9, 5) + def test_130_order_proj_north_east(self): - self.params130['crs'] = 'EPSG:31466' + self.params130["crs"] = "EPSG:31466" req130 = WMS130MapRequest(self.params130) - eq_(req130.params.bbox, (4, 8, 5, 9)) + assert req130.params.bbox == (4, 8, 5, 9) + def test_130_order_proj(self): - self.params130['crs'] = 'EPSG:31463' + self.params130["crs"] = "EPSG:31463" req130 = WMS130MapRequest(self.params130) - eq_(req130.params.bbox, (8, 4, 9, 5)) + assert req130.params.bbox == (8, 4, 9, 5) + class TestTileRequest(object): + def test_tms_request(self): - env = { - 'PATH_INFO': '/tms/1.0.0/osm/5/2/3.png', - 'QUERY_STRING': '', - } + env = {"PATH_INFO": "/tms/1.0.0/osm/5/2/3.png", "QUERY_STRING": ""} req = Request(env) tms = tile_request(req) assert isinstance(tms, TMSRequest) - eq_(tms.tile, (2, 3, 5)) - eq_(tms.format, 'png') - eq_(tms.layer, 'osm') - eq_(tms.dimensions, {}) + assert tms.tile == (2, 3, 5) + assert tms.format == "png" + assert tms.layer == "osm" + assert tms.dimensions == {} def test_tile_request(self): - env = { - 'PATH_INFO': '/tiles/1.0.0/osm/5/2/3.png', - 'QUERY_STRING': '', - } + env = {"PATH_INFO": "/tiles/1.0.0/osm/5/2/3.png", "QUERY_STRING": ""} req = Request(env) tile_req = tile_request(req) assert isinstance(tile_req, TileRequest) - eq_(tile_req.tile, (2, 3, 5)) - eq_(tile_req.origin, None) - eq_(tile_req.format, 'png') - eq_(tile_req.layer, 'osm') - eq_(tile_req.dimensions, {}) + assert tile_req.tile == (2, 3, 5) + assert tile_req.origin == None + assert tile_req.format == "png" + assert tile_req.layer == "osm" + assert tile_req.dimensions == {} def test_tile_request_flipped_y(self): - env = { - 'PATH_INFO': '/tiles/1.0.0/osm/5/2/3.png', - 'QUERY_STRING': 'origin=nw', - } + env = {"PATH_INFO": "/tiles/1.0.0/osm/5/2/3.png", "QUERY_STRING": "origin=nw"} req = Request(env) tile_req = tile_request(req) assert isinstance(tile_req, TileRequest) - eq_(tile_req.tile, (2, 3, 5)) # not jet flipped - eq_(tile_req.origin, 'nw') - eq_(tile_req.format, 'png') - eq_(tile_req.layer, 'osm') - eq_(tile_req.dimensions, {}) + assert tile_req.tile == (2, 3, 5) # not yet flipped + assert tile_req.origin == "nw" + assert tile_req.format == "png" + assert tile_req.layer == "osm" + assert tile_req.dimensions == {} def test_tile_request_w_epsg(self): - env = { - 'PATH_INFO': '/tiles/1.0.0/osm/EPSG4326/5/2/3.png', - 'QUERY_STRING': '', - } + env = {"PATH_INFO": "/tiles/1.0.0/osm/EPSG4326/5/2/3.png", "QUERY_STRING": ""} req = Request(env) tile_req = tile_request(req) assert isinstance(tile_req, TileRequest) - eq_(tile_req.tile, (2, 3, 5)) - eq_(tile_req.format, 'png') - eq_(tile_req.layer, 'osm') - eq_(tile_req.dimensions, {'_layer_spec': 'EPSG4326'}) + assert tile_req.tile == (2, 3, 5) + assert tile_req.format == "png" + assert tile_req.layer == "osm" + assert tile_req.dimensions == {"_layer_spec": "EPSG4326"} + def test_request_params_pickle(): - params = RequestParams(dict(foo='bar', zing='zong')) + params = RequestParams(dict(foo="bar", zing="zong")) params2 = pickle.loads(pickle.dumps(params, 2)) assert params.params == params2.params - diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_request_wmts.py mapproxy-1.12.0/mapproxy/test/unit/test_request_wmts.py --- mapproxy-1.11.0/mapproxy/test/unit/test_request_wmts.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_request_wmts.py 2019-08-30 07:34:08.000000000 +0000 @@ -13,63 +13,166 @@ # See the License for the specific language governing permissions and # limitations under the License. +from mapproxy.exception import RequestError from mapproxy.request.wmts import wmts_request, WMTS100CapabilitiesRequest -from mapproxy.request.wmts import URLTemplateConverter, InvalidWMTSTemplate +from mapproxy.request.wmts import ( + URLTemplateConverter, + FeatureInfoURLTemplateConverter, + InvalidWMTSTemplate, + make_wmts_rest_request_parser, + WMTS100RestTileRequest, + WMTS100RestFeatureInfoRequest, +) from mapproxy.request.base import url_decode -from nose.tools import eq_, raises +import pytest + + +def dummy_req(args): + return DummyRequest(url_decode(args.replace("\n", ""))) + + +def dummy_rest_req(path): + return DummyRequest(args="", path=path) -def dummy_req(url): - return DummyRequest(url_decode(url.replace('\n', ''))) class DummyRequest(object): - def __init__(self, args, url=''): + + def __init__(self, args, path="", url=""): self.args = args + self.path = path self.base_url = url + def test_tile_request(): - url = '''requeST=GetTile&service=wmts&tileMatrixset=EPSG900913& -tilematrix=2&tileROW=4&TILECOL=2&FORMAT=image/png&Style=&layer=Foo&version=1.0.0''' - req = wmts_request(dummy_req(url)) - - eq_(req.params.coord, (2, 4, '2')) - eq_(req.params.layer, 'Foo') - eq_(req.params.format, 'png') - eq_(req.params.tilematrixset, 'EPSG900913') + args = """requeST=GetTile&service=wmts&tileMatrixset=EPSG900913& +tilematrix=2&tileROW=4&TILECOL=2&FORMAT=image/png&Style=&layer=Foo&version=1.0.0""" + req = wmts_request(dummy_req(args)) + + assert req.params.coord == (2, 4, "2") + assert req.params.layer == "Foo" + assert req.params.format == "png" + assert req.params.tilematrixset == "EPSG900913" + + +def test_featureinfo_request(): + args = """requeST=GetFeatureInfo&service=wmts&tileMatrixset=EPSG900913& +tilematrix=2&tileROW=4&TILECOL=2&FORMAT=image/png&Style=&layer=Foo&version=1.0.0& +i=5&j=10&infoformat=application/json""" + req = wmts_request(dummy_req(args)) + + assert req.params.coord == (2, 4, "2") + assert req.params.layer == "Foo" + assert req.params.format == "png" + assert req.params.tilematrixset == "EPSG900913" + assert req.params.pos == (5, 10) + assert req.params.infoformat == "application/json" + def test_capabilities_request(): - url = '''requeST=GetCapabilities&service=wmts''' - req = wmts_request(dummy_req(url)) + args = """requeST=GetCapabilities&service=wmts""" + req = wmts_request(dummy_req(args)) assert isinstance(req, WMTS100CapabilitiesRequest) + def test_template_converter(): - regexp = URLTemplateConverter('/{Layer}/{Style}/{TileMatrixSet}-{TileMatrix}-{TileCol}-{TileRow}/tile').regexp() - match = regexp.match('/test/bar/foo-EPSG4326-4-12-99/tile') + regexp = URLTemplateConverter( + "/{Layer}/{Style}/{TileMatrixSet}-{TileMatrix}-{TileCol}-{TileRow}/tile" + ).regexp() + match = regexp.match("/wmts/test/bar/foo-EPSG4326-4-12-99/tile") assert match - assert match.groupdict()['Layer'] == 'test' - assert match.groupdict()['TileMatrixSet'] == 'foo-EPSG4326' - assert match.groupdict()['TileMatrix'] == '4' - assert match.groupdict()['TileCol'] == '12' - assert match.groupdict()['TileRow'] == '99' - assert match.groupdict()['Style'] == 'bar' + assert match.groupdict()["Layer"] == "test" + assert match.groupdict()["TileMatrixSet"] == "foo-EPSG4326" + assert match.groupdict()["TileMatrix"] == "4" + assert match.groupdict()["TileCol"] == "12" + assert match.groupdict()["TileRow"] == "99" + assert match.groupdict()["Style"] == "bar" + def test_template_converter_deprecated_format(): # old format that doesn't match the WMTS spec, now deprecated - regexp = URLTemplateConverter('/{{Layer}}/{{Style}}/{{TileMatrixSet}}-{{TileMatrix}}-{{TileCol}}-{{TileRow}}/tile').regexp() - match = regexp.match('/test/bar/foo-EPSG4326-4-12-99/tile') + regexp = URLTemplateConverter( + "/{{Layer}}/{{Style}}/{{TileMatrixSet}}-{{TileMatrix}}-{{TileCol}}-{{TileRow}}/tile" + ).regexp() + match = regexp.match("/wmts/test/bar/foo-EPSG4326-4-12-99/tile") assert match - assert match.groupdict()['Layer'] == 'test' - assert match.groupdict()['TileMatrixSet'] == 'foo-EPSG4326' - assert match.groupdict()['TileMatrix'] == '4' - assert match.groupdict()['TileCol'] == '12' - assert match.groupdict()['TileRow'] == '99' - assert match.groupdict()['Style'] == 'bar' + assert match.groupdict()["Layer"] == "test" + assert match.groupdict()["TileMatrixSet"] == "foo-EPSG4326" + assert match.groupdict()["TileMatrix"] == "4" + assert match.groupdict()["TileCol"] == "12" + assert match.groupdict()["TileRow"] == "99" + assert match.groupdict()["Style"] == "bar" + -@raises(InvalidWMTSTemplate) def test_template_converter_missing_vars(): - URLTemplateConverter('/wmts/{Style}/{TileMatrixSet}/{TileCol}.png').regexp() + with pytest.raises(InvalidWMTSTemplate): + URLTemplateConverter("/wmts/{Style}/{TileMatrixSet}/{TileCol}.png").regexp() + def test_template_converter_dimensions(): - converter = URLTemplateConverter('/{Layer}/{Dim1}/{Dim2}/{TileMatrixSet}-{TileMatrix}-{TileCol}-{TileRow}/tile') - assert converter.dimensions == ['Dim1', 'Dim2'] + converter = URLTemplateConverter( + "/{Layer}/{Dim1}/{Dim2}/{TileMatrixSet}-{TileMatrix}-{TileCol}-{TileRow}/tile" + ) + assert converter.dimensions == ["Dim1", "Dim2"] + + +class TestRestRequestParser(object): + + @pytest.fixture + def parser(self): + return make_wmts_rest_request_parser( + URLTemplateConverter( + "/{Layer}/{TileMatrixSet}/{TileMatrix}/{TileCol}/{TileRow}.{Format}" + ), + FeatureInfoURLTemplateConverter( + "/{Layer}/{TileMatrixSet}/{TileMatrix}/{TileCol}/{TileRow}/{I}/{J}.{InfoFormat}" + ), + ) + + @pytest.mark.parametrize( + "url,tile,pos", + [ + ["/wmts/roads/webmercator/09/201/123/10/30.json", (201, 123, 9), (10, 30)], + ["/wmts/roads/webmercator/10/201/1/0/999.json", (201, 1, 10), (0, 999)], + ["/wmts/roads/webmercator/09/201/123/10/30json", None, None], + ["/wmts/roads/webmercator/09/201/123/10.json", None, None], + ["/wmts/roads-webmercator/09/201/123/10/30.json", None, None], + ["/roads/webmercator/09/201/123/10/30.json", None, None], + ], + ) + def test_featureinfo(self, parser, url, tile, pos): + if tile is None: + with pytest.raises(RequestError): + parser(dummy_rest_req(url)) + else: + req = parser(dummy_rest_req(url)) + assert isinstance(req, WMTS100RestFeatureInfoRequest) + req.make_request() + assert req.pos == pos + assert req.tile == tile + assert req.infoformat == "json" + assert req.tilematrixset == "webmercator" + + @pytest.mark.parametrize( + "url,tile", + [ + ["/wmts/roads/webmercator/09/201/123.png", (201, 123, 9)], + ["/wmts/roads/webmercator/10/201/123.png", (201, 123, 10)], + ["/wmts/roads/webmercator/10/201/123a.png", None], + ["/wmts/roads/webmercator/10/201/123png", None], + ["/wmts/roads/webmercator/10/2013.png", None], + ["/wmts/roads-webmercator/10/201/123.png", None], + ], + ) + def test_tile(self, parser, url, tile): + if tile is None: + with pytest.raises(RequestError): + parser(dummy_rest_req(url)) + else: + req = parser(dummy_rest_req(url)) + assert isinstance(req, WMTS100RestTileRequest) + req.make_request() + assert req.tile == tile + assert req.format == "png" + assert req.tilematrixset == "webmercator" diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_response.py mapproxy-1.12.0/mapproxy/test/unit/test_response.py --- mapproxy-1.11.0/mapproxy/test/unit/test_response.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_response.py 2019-08-30 07:34:08.000000000 +0000 @@ -20,56 +20,60 @@ from mapproxy.response import Response from mapproxy.compat import string_type + class TestResponse(Mocker): + def test_str_response(self): - resp = Response('string content') + resp = Response("string content") assert isinstance(resp.response, string_type) start_response = self.mock() - self.expect(start_response('200 OK', ANY)) + self.expect(start_response("200 OK", ANY)) self.replay() - result = resp({'REQUEST_METHOD': 'GET'}, start_response) - assert next(result) == b'string content' + result = resp({"REQUEST_METHOD": "GET"}, start_response) + assert next(result) == b"string content" def test_itr_response(self): - resp = Response(iter(['string content', 'as iterable'])) - assert hasattr(resp.response, 'next') or hasattr(resp.response, '__next__') + resp = Response(iter(["string content", "as iterable"])) + assert hasattr(resp.response, "next") or hasattr(resp.response, "__next__") start_response = self.mock() - self.expect(start_response('200 OK', ANY)) + self.expect(start_response("200 OK", ANY)) self.replay() - result = resp({'REQUEST_METHOD': 'GET'}, start_response) - assert next(result) == 'string content' - assert next(result) == 'as iterable' + result = resp({"REQUEST_METHOD": "GET"}, start_response) + assert next(result) == "string content" + assert next(result) == "as iterable" def test_file_response(self): - data = BytesIO(b'foobar') + data = BytesIO(b"foobar") resp = Response(data) assert resp.response == data start_response = self.mock() - self.expect(start_response('200 OK', ANY)) + self.expect(start_response("200 OK", ANY)) self.replay() - result = resp({'REQUEST_METHOD': 'GET'}, start_response) - assert next(result) == b'foobar' + result = resp({"REQUEST_METHOD": "GET"}, start_response) + assert next(result) == b"foobar" def test_file_response_w_file_wrapper(self): - data = BytesIO(b'foobar') + data = BytesIO(b"foobar") resp = Response(data) assert resp.response == data start_response = self.mock() - self.expect(start_response('200 OK', ANY)) + self.expect(start_response("200 OK", ANY)) file_wrapper = self.mock() - self.expect(file_wrapper(data, resp.block_size)).result('DUMMY') + self.expect(file_wrapper(data, resp.block_size)).result("DUMMY") self.replay() - result = resp({'REQUEST_METHOD': 'GET', - 'wsgi.file_wrapper': file_wrapper}, start_response) - assert result == 'DUMMY' + result = resp( + {"REQUEST_METHOD": "GET", "wsgi.file_wrapper": file_wrapper}, start_response + ) + assert result == "DUMMY" + def test_file_response_content_length(self): - data = BytesIO(b'*' * 342) + data = BytesIO(b"*" * 342) resp = Response(data) assert resp.response == data start_response = self.mock() - self.expect(start_response('200 OK', ANY)) + self.expect(start_response("200 OK", ANY)) self.replay() - resp({'REQUEST_METHOD': 'GET'}, start_response) + resp({"REQUEST_METHOD": "GET"}, start_response) assert resp.content_length == 342 diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_seed_cachelock.py mapproxy-1.12.0/mapproxy/test/unit/test_seed_cachelock.py --- mapproxy-1.11.0/mapproxy/test/unit/test_seed_cachelock.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_seed_cachelock.py 2019-08-30 07:34:08.000000000 +0000 @@ -1,12 +1,12 @@ # This file is part of the MapProxy project. # Copyright (C) 2012 Omniscale <http://omniscale.de> -# +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,75 +14,76 @@ # limitations under the License. import multiprocessing -import os -import shutil -import tempfile import time +import sys + +import pytest from mapproxy.seed.cachelock import CacheLocker, CacheLockedError + +@pytest.mark.skipif(sys.platform == "win32", reason="test not supported for Windows") class TestCacheLock(object): - - def setup(self): - self.tmp_dir = tempfile.mkdtemp() - self.lock_file = os.path.join(self.tmp_dir, 'lock') - - def teardown(self): - shutil.rmtree(self.tmp_dir) - - def test_free_lock(self): - locker = CacheLocker(self.lock_file) - with locker.lock('foo'): + + @pytest.fixture + def lock_file(self, tmpdir): + return (tmpdir / "lock").strpath + + def test_free_lock(self, lock_file): + locker = CacheLocker(lock_file) + with locker.lock("foo"): assert True - - def test_locked_by_process_no_block(self): + + def test_locked_by_process_no_block(self, lock_file): proc_is_locked = multiprocessing.Event() + def lock(): - locker = CacheLocker(self.lock_file) - with locker.lock('foo'): + locker = CacheLocker(lock_file) + with locker.lock("foo"): proc_is_locked.set() time.sleep(10) - + p = multiprocessing.Process(target=lock) p.start() # wait for process to start proc_is_locked.wait() - - locker = CacheLocker(self.lock_file) - + + locker = CacheLocker(lock_file) + # test unlocked bar - with locker.lock('bar', no_block=True): + with locker.lock("bar", no_block=True): assert True - + # test locked foo try: - with locker.lock('foo', no_block=True): + with locker.lock("foo", no_block=True): assert False except CacheLockedError: pass finally: p.terminate() p.join() - - def test_locked_by_process_waiting(self): + + def test_locked_by_process_waiting(self, lock_file): proc_is_locked = multiprocessing.Event() + def lock(): - locker = CacheLocker(self.lock_file) - with locker.lock('foo'): + locker = CacheLocker(lock_file) + with locker.lock("foo"): proc_is_locked.set() time.sleep(.1) - + p = multiprocessing.Process(target=lock) start_time = time.time() p.start() # wait for process to start proc_is_locked.wait() - - locker = CacheLocker(self.lock_file, polltime=0.02) + + locker = CacheLocker(lock_file, polltime=0.02) try: - with locker.lock('foo', no_block=False): + with locker.lock("foo", no_block=False): diff = time.time() - start_time assert diff > 0.1 finally: p.terminate() - p.join() \ No newline at end of file + p.join() diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_seed.py mapproxy-1.12.0/mapproxy/test/unit/test_seed.py --- mapproxy-1.11.0/mapproxy/test/unit/test_seed.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_seed.py 2019-08-30 07:34:08.000000000 +0000 @@ -17,11 +17,15 @@ import os import time +from collections import defaultdict + try: import cPickle as pickle except ImportError: import pickle +import pytest + from mapproxy.seed.seeder import TileWalker, SeedTask, SeedProgress from mapproxy.cache.dummy import DummyLocker from mapproxy.cache.tile import TileManager @@ -31,78 +35,94 @@ from mapproxy.srs import SRS from mapproxy.util.coverage import BBOXCoverage, GeomCoverage from mapproxy.seed.config import before_timestamp_from_options, SeedConfigurationError -from mapproxy.seed.config import LevelsList, LevelsRange, LevelsResolutionList, LevelsResolutionRange +from mapproxy.seed.config import ( + LevelsList, + LevelsRange, + LevelsResolutionList, + LevelsResolutionRange, +) from mapproxy.seed.util import ProgressStore from mapproxy.test.helper import TempFile -from collections import defaultdict -from nose.tools import eq_, assert_almost_equal, raises -from nose.plugins.skip import SkipTest try: from shapely.wkt import loads as load_wkt - load_wkt # prevent lint warning + + load_wkt # prevent lint warning except ImportError: load_wkt = None + class MockSeedPool(object): + def __init__(self): self.seeded_tiles = defaultdict(set) + def process(self, tiles, progess): for x, y, level in tiles: self.seeded_tiles[level].add((x, y)) + class MockCache(object): + def is_cached(self, tile): return False + class TestSeeder(object): + def setup(self): self.grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90]) self.source = TiledSource(self.grid, None) - self.tile_mgr = TileManager(self.grid, MockCache(), [self.source], 'png', - locker=DummyLocker()) + self.tile_mgr = TileManager( + self.grid, MockCache(), [self.source], "png", locker=DummyLocker() + ) self.seed_pool = MockSeedPool() def make_bbox_task(self, bbox, srs, levels): - md = dict(name='', cache_name='', grid_name='') + md = dict(name="", cache_name="", grid_name="") coverage = BBOXCoverage(bbox, srs) - return SeedTask(md, self.tile_mgr, levels, refresh_timestamp=None, coverage=coverage) + return SeedTask( + md, self.tile_mgr, levels, refresh_timestamp=None, coverage=coverage + ) def make_geom_task(self, geom, srs, levels): - md = dict(name='', cache_name='', grid_name='') + md = dict(name="", cache_name="", grid_name="") coverage = GeomCoverage(geom, srs) - return SeedTask(md, self.tile_mgr, levels, refresh_timestamp=None, coverage=coverage) + return SeedTask( + md, self.tile_mgr, levels, refresh_timestamp=None, coverage=coverage + ) def test_seed_full_bbox(self): task = self.make_bbox_task([-180, -90, 180, 90], SRS(4326), [0, 1, 2]) seeder = TileWalker(task, self.seed_pool, handle_uncached=True) seeder.walk() - eq_(len(self.seed_pool.seeded_tiles), 3) - eq_(self.seed_pool.seeded_tiles[0], set([(0, 0)])) - eq_(self.seed_pool.seeded_tiles[1], set([(0, 0), (1, 0)])) - eq_(self.seed_pool.seeded_tiles[2], set([(0, 0), (1, 0), (2, 0), (3, 0), - (0, 1), (1, 1), (2, 1), (3, 1)])) + assert len(self.seed_pool.seeded_tiles) == 3 + assert self.seed_pool.seeded_tiles[0] == set([(0, 0)]) + assert self.seed_pool.seeded_tiles[1] == set([(0, 0), (1, 0)]) + assert self.seed_pool.seeded_tiles[2] == set( + [(0, 0), (1, 0), (2, 0), (3, 0), (0, 1), (1, 1), (2, 1), (3, 1)] + ) def test_seed_small_bbox(self): task = self.make_bbox_task([-45, 0, 180, 90], SRS(4326), [0, 1, 2]) seeder = TileWalker(task, self.seed_pool, handle_uncached=True) seeder.walk() - eq_(len(self.seed_pool.seeded_tiles), 3) - eq_(self.seed_pool.seeded_tiles[0], set([(0, 0)])) - eq_(self.seed_pool.seeded_tiles[1], set([(0, 0), (1, 0)])) - eq_(self.seed_pool.seeded_tiles[2], set([(1, 1), (2, 1), (3, 1)])) + assert len(self.seed_pool.seeded_tiles) == 3 + assert self.seed_pool.seeded_tiles[0] == set([(0, 0)]) + assert self.seed_pool.seeded_tiles[1] == set([(0, 0), (1, 0)]) + assert self.seed_pool.seeded_tiles[2] == set([(1, 1), (2, 1), (3, 1)]) def test_seed_small_bbox_iregular_levels(self): task = self.make_bbox_task([-45, 0, 180, 90], SRS(4326), [0, 2]) seeder = TileWalker(task, self.seed_pool, handle_uncached=True) seeder.walk() - eq_(len(self.seed_pool.seeded_tiles), 2) - eq_(self.seed_pool.seeded_tiles[0], set([(0, 0)])) - eq_(self.seed_pool.seeded_tiles[2], set([(1, 1), (2, 1), (3, 1)])) + assert len(self.seed_pool.seeded_tiles) == 2 + assert self.seed_pool.seeded_tiles[0] == set([(0, 0)]) + assert self.seed_pool.seeded_tiles[2] == set([(1, 1), (2, 1), (3, 1)]) def test_seed_small_bbox_transformed(self): bbox = SRS(4326).transform_bbox_to(SRS(900913), [-45, 0, 179, 80]) @@ -110,127 +130,157 @@ seeder = TileWalker(task, self.seed_pool, handle_uncached=True) seeder.walk() - eq_(len(self.seed_pool.seeded_tiles), 3) - eq_(self.seed_pool.seeded_tiles[0], set([(0, 0)])) - eq_(self.seed_pool.seeded_tiles[1], set([(0, 0), (1, 0)])) - eq_(self.seed_pool.seeded_tiles[2], set([(1, 1), (2, 1), (3, 1)])) + assert len(self.seed_pool.seeded_tiles) == 3 + assert self.seed_pool.seeded_tiles[0] == set([(0, 0)]) + assert self.seed_pool.seeded_tiles[1] == set([(0, 0), (1, 0)]) + assert self.seed_pool.seeded_tiles[2] == set([(1, 1), (2, 1), (3, 1)]) + @pytest.mark.skipif(not load_wkt, reason="Shapely not installed") def test_seed_with_geom(self): - if not load_wkt: raise SkipTest('no shapely installed') # box from 10 10 to 80 80 with small spike/corner to -10 60 (upper left) geom = load_wkt("POLYGON((10 10, 10 50, -10 60, 10 80, 80 80, 80 10, 10 10))") task = self.make_geom_task(geom, SRS(4326), [0, 1, 2, 3, 4]) seeder = TileWalker(task, self.seed_pool, handle_uncached=True) seeder.walk() - eq_(len(self.seed_pool.seeded_tiles), 5) - eq_(self.seed_pool.seeded_tiles[0], set([(0, 0)])) - eq_(self.seed_pool.seeded_tiles[1], set([(0, 0), (1, 0)])) - eq_(self.seed_pool.seeded_tiles[2], set([(1, 1), (2, 1)])) - eq_(self.seed_pool.seeded_tiles[3], set([(4, 2), (5, 2), (4, 3), (5, 3), (3, 3)])) - eq_(len(self.seed_pool.seeded_tiles[4]), 4*4+2) + assert len(self.seed_pool.seeded_tiles) == 5 + assert self.seed_pool.seeded_tiles[0] == set([(0, 0)]) + assert self.seed_pool.seeded_tiles[1] == set([(0, 0), (1, 0)]) + assert self.seed_pool.seeded_tiles[2] == set([(1, 1), (2, 1)]) + assert self.seed_pool.seeded_tiles[3] == set( + [(4, 2), (5, 2), (4, 3), (5, 3), (3, 3)] + ) + assert len(self.seed_pool.seeded_tiles[4]) == 4 * 4 + 2 + @pytest.mark.skipif(not load_wkt, reason="Shapely not installed") def test_seed_with_res_list(self): - if not load_wkt: raise SkipTest('no shapely installed') # box from 10 10 to 80 80 with small spike/corner to -10 60 (upper left) geom = load_wkt("POLYGON((10 10, 10 50, -10 60, 10 80, 80 80, 80 10, 10 10))") - self.grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90], - res=[360/256, 360/720, 360/2000, 360/5000, 360/8000]) - self.tile_mgr = TileManager(self.grid, MockCache(), [self.source], 'png', - locker=DummyLocker()) + self.grid = TileGrid( + SRS(4326), + bbox=[-180, -90, 180, 90], + res=[360 / 256, 360 / 720, 360 / 2000, 360 / 5000, 360 / 8000], + ) + self.tile_mgr = TileManager( + self.grid, MockCache(), [self.source], "png", locker=DummyLocker() + ) task = self.make_geom_task(geom, SRS(4326), [0, 1, 2, 3, 4]) seeder = TileWalker(task, self.seed_pool, handle_uncached=True) seeder.walk() - eq_(len(self.seed_pool.seeded_tiles), 5) - eq_(self.seed_pool.seeded_tiles[0], set([(0, 0)])) - eq_(self.grid.grid_sizes[1], (3, 2)) - eq_(self.seed_pool.seeded_tiles[1], set([(1, 0), (1, 1), (2, 0), (2, 1)])) - eq_(self.grid.grid_sizes[2], (8, 4)) - eq_(self.seed_pool.seeded_tiles[2], set([(4, 2), (5, 2), (4, 3), (5, 3), (3, 3)])) - eq_(self.grid.grid_sizes[3], (20, 10)) - eq_(len(self.seed_pool.seeded_tiles[3]), 5*5+2) + assert len(self.seed_pool.seeded_tiles) == 5 + assert self.seed_pool.seeded_tiles[0] == set([(0, 0)]) + assert self.grid.grid_sizes[1] == (3, 2) + assert self.seed_pool.seeded_tiles[1] == set([(1, 0), (1, 1), (2, 0), (2, 1)]) + assert self.grid.grid_sizes[2] == (8, 4) + assert self.seed_pool.seeded_tiles[2] == set( + [(4, 2), (5, 2), (4, 3), (5, 3), (3, 3)] + ) + assert self.grid.grid_sizes[3] == (20, 10) + assert len(self.seed_pool.seeded_tiles[3]) == 5 * 5 + 2 def test_seed_full_bbox_continue(self): task = self.make_bbox_task([-180, -90, 180, 90], SRS(4326), [0, 1, 2]) seed_progress = SeedProgress([(0, 1), (1, 2)]) - seeder = TileWalker(task, self.seed_pool, handle_uncached=True, seed_progress=seed_progress) + seeder = TileWalker( + task, self.seed_pool, handle_uncached=True, seed_progress=seed_progress + ) seeder.walk() - eq_(len(self.seed_pool.seeded_tiles), 3) - eq_(self.seed_pool.seeded_tiles[0], set([(0, 0)])) - eq_(self.seed_pool.seeded_tiles[1], set([(0, 0), (1, 0)])) - eq_(self.seed_pool.seeded_tiles[2], set([(2, 0), (3, 0), - (2, 1), (3, 1)])) + assert len(self.seed_pool.seeded_tiles) == 3 + assert self.seed_pool.seeded_tiles[0] == set([(0, 0)]) + assert self.seed_pool.seeded_tiles[1] == set([(0, 0), (1, 0)]) + assert self.seed_pool.seeded_tiles[2] == set([(2, 0), (3, 0), (2, 1), (3, 1)]) + class TestLevels(object): + def test_level_list(self): levels = LevelsList([-10, 3, 1, 3, 5, 7, 50]) - eq_(levels.for_grid(tile_grid_for_epsg(4326)), - [1, 3, 5, 7]) + assert levels.for_grid(tile_grid_for_epsg(4326)) == [1, 3, 5, 7] def test_level_range(self): levels = LevelsRange([1, 5]) - eq_(levels.for_grid(tile_grid_for_epsg(4326)), - [1, 2, 3, 4, 5]) + assert levels.for_grid(tile_grid_for_epsg(4326)) == [1, 2, 3, 4, 5] def test_level_range_open_from(self): levels = LevelsRange([None, 5]) - eq_(levels.for_grid(tile_grid_for_epsg(4326)), - [0, 1, 2, 3, 4, 5]) + assert levels.for_grid(tile_grid_for_epsg(4326)) == [0, 1, 2, 3, 4, 5] def test_level_range_open_to(self): levels = LevelsRange([13, None]) - eq_(levels.for_grid(tile_grid_for_epsg(4326)), - [13, 14, 15, 16, 17, 18, 19]) + assert levels.for_grid(tile_grid_for_epsg(4326)) == [13, 14, 15, 16, 17, 18, 19] def test_level_range_open_tos_range(self): levels = LevelsResolutionRange([1000, 100]) - eq_(levels.for_grid(tile_grid_for_epsg(900913)), - [8, 9, 10, 11]) + assert levels.for_grid(tile_grid_for_epsg(900913)) == [8, 9, 10, 11] def test_res_range_open_from(self): levels = LevelsResolutionRange([None, 100]) - eq_(levels.for_grid(tile_grid_for_epsg(900913)), - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) + assert levels.for_grid(tile_grid_for_epsg(900913)) == [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + ] def test_res_range_open_to(self): levels = LevelsResolutionRange([1000, None]) - eq_(levels.for_grid(tile_grid_for_epsg(900913)), - [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) + assert levels.for_grid(tile_grid_for_epsg(900913)) == [ + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + ] def test_resolution_list(self): levels = LevelsResolutionList([1000, 100, 500]) - eq_(levels.for_grid(tile_grid_for_epsg(900913)), - [8, 9, 11]) + assert levels.for_grid(tile_grid_for_epsg(900913)) == [8, 9, 11] class TestProgressStore(object): + def test_load_empty(self): - store = ProgressStore('doesnotexist.no_realy.txt') + store = ProgressStore("doesnotexist.no_realy.txt") store.load() - assert store.get(('foo', 'bar', 'baz')) == None + assert store.get(("foo", "bar", "baz")) == None def test_load_store(self): with TempFile(no_create=True) as tmp: - with open(tmp, 'wb') as f: + with open(tmp, "wb") as f: f.write(pickle.dumps({("view", "cache", "grid"): [(0, 1), (2, 4)]})) store = ProgressStore(tmp) - assert store.get(('view', 'cache', 'grid')) == [(0, 1), (2, 4)] - assert store.get(('view', 'cache', 'grid2')) == None - store.add(('view', 'cache', 'grid'), []) - store.add(('view', 'cache', 'grid2'), [(0, 1)]) + assert store.get(("view", "cache", "grid")) == [(0, 1), (2, 4)] + assert store.get(("view", "cache", "grid2")) == None + store.add(("view", "cache", "grid"), []) + store.add(("view", "cache", "grid2"), [(0, 1)]) store.write() store = ProgressStore(tmp) - assert store.get(('view', 'cache', 'grid')) == [] - assert store.get(('view', 'cache', 'grid2')) == [(0, 1)] + assert store.get(("view", "cache", "grid")) == [] + assert store.get(("view", "cache", "grid2")) == [(0, 1)] def test_load_broken(self): with TempFile(no_create=True) as tmp: - with open(tmp, 'wb') as f: - f.write(b'##invaliddata') + with open(tmp, "wb") as f: + f.write(b"##invaliddata") f.write(pickle.dumps({("view", "cache", "grid"): [(0, 1), (2, 4)]})) store = ProgressStore(tmp) @@ -238,51 +288,52 @@ class TestRemovebreforeTimetamp(object): + def test_from_time(self): - ts = before_timestamp_from_options({'time': '2010-12-01T20:12:00'}) + ts = before_timestamp_from_options({"time": "2010-12-01T20:12:00"}) # we don't know the timezone this test will run assert (1291230720.0 - 14 * 3600) < ts < (1291230720.0 + 14 * 3600) def test_from_mtime(self): with TempFile() as tmp: os.utime(tmp, (12376512, 12376512)) - eq_(before_timestamp_from_options({'mtime': tmp}), 12376512) + assert before_timestamp_from_options({"mtime": tmp}) == 12376512 - @raises(SeedConfigurationError) def test_from_mtime_missing_file(self): - before_timestamp_from_options({'mtime': '/tmp/does-not-exist-at-all,really'}) + with pytest.raises(SeedConfigurationError): + before_timestamp_from_options( + {"mtime": "/tmp/does-not-exist-at-all,really"} + ) def test_from_empty(self): - assert_almost_equal( - before_timestamp_from_options({}), - time.time(), -1 - ) + assert before_timestamp_from_options({}) == pytest.approx(time.time(), 1) def test_from_delta(self): - assert_almost_equal( - before_timestamp_from_options({'minutes': 15}) + 60 * 15, - time.time(), -1 - ) + assert ( + before_timestamp_from_options({"minutes": 15}) + 60 * 15 + ) == pytest.approx(time.time(), 1) + class TestSeedProgress(object): + def test_progress_identifier(self): old = SeedProgress() with old.step_down(0, 2): with old.step_down(0, 4): - eq_(old.current_progress_identifier(), [(0, 2), (0, 4)]) + assert old.current_progress_identifier() == [(0, 2), (0, 4)] # previous leafs are still present - eq_(old.current_progress_identifier(), [(0, 2), (0, 4)]) + assert old.current_progress_identifier() == [(0, 2), (0, 4)] with old.step_down(1, 4): - eq_(old.current_progress_identifier(), [(0, 2), (1, 4)]) - eq_(old.current_progress_identifier(), [(0, 2), (1, 4)]) + assert old.current_progress_identifier() == [(0, 2), (1, 4)] + assert old.current_progress_identifier() == [(0, 2), (1, 4)] - eq_(old.current_progress_identifier(), []) # empty list after seed + assert old.current_progress_identifier() == [] # empty list after seed with old.step_down(1, 2): - eq_(old.current_progress_identifier(), [(1, 2)]) + assert old.current_progress_identifier() == [(1, 2)] with old.step_down(0, 4): with old.step_down(1, 4): - eq_(old.current_progress_identifier(), [(1, 2), (0, 4), (1, 4)]) + assert old.current_progress_identifier() == [(1, 2), (0, 4), (1, 4)] def test_already_processed(self): new = SeedProgress([(0, 2)]) @@ -297,7 +348,6 @@ with new.step_down(0, 2): assert new.already_processed() - new = SeedProgress([(0, 2), (1, 4), (2, 4)]) with new.step_down(0, 2): assert not new.already_processed() diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_srs.py mapproxy-1.12.0/mapproxy/test/unit/test_srs.py --- mapproxy-1.11.0/mapproxy/test/unit/test_srs.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_srs.py 2019-08-30 07:34:08.000000000 +0000 @@ -1,12 +1,12 @@ # This file is part of the MapProxy project. # Copyright (C) 2010 Omniscale <http://omniscale.de> -# +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,81 +14,162 @@ # limitations under the License. import os -from mapproxy.config import base_config -from mapproxy import srs -from mapproxy.srs import SRS -class Test_0_ProjDefaultDataPath(object): - - def test_known_srs(self): - srs.SRS(4326) - - def test_unknown_srs(self): - try: - srs.SRS(1234) - except RuntimeError: - pass - else: - assert False, 'RuntimeError expected' - +import pytest -class Test_1_ProjDataPath(object): - - def setup(self): - srs._proj_initalized = False - srs._srs_cache = {} - base_config().srs.proj_data_dir = os.path.dirname(__file__) - - def test_dummy_srs(self): - srs.SRS(1234) - - def test_unknown_srs(self): - try: - srs.SRS(2339) - except RuntimeError: - pass - else: - assert False, 'RuntimeError expected' - - def teardown(self): - srs._proj_initalized = False - srs._srs_cache = {} - base_config().srs.proj_data_dir = None +from mapproxy.config import base_config +from mapproxy import srs +from mapproxy.srs import SRS, PreferredSrcSRS, SupportedSRS class TestSRS(object): + def test_epsg4326(self): srs = SRS(4326) - + assert srs.is_latlong assert not srs.is_axis_order_en assert srs.is_axis_order_ne - + def test_crs84(self): - srs = SRS('CRS:84') - + srs = SRS("CRS:84") + assert srs.is_latlong assert srs.is_axis_order_en assert not srs.is_axis_order_ne - assert srs == SRS('EPSG:4326') + assert srs == SRS("EPSG:4326") def test_epsg31467(self): - srs = SRS('EPSG:31467') - + srs = SRS("EPSG:31467") + assert not srs.is_latlong assert not srs.is_axis_order_en assert srs.is_axis_order_ne def test_epsg900913(self): - srs = SRS('epsg:900913') - + srs = SRS("epsg:900913") + assert not srs.is_latlong assert srs.is_axis_order_en assert not srs.is_axis_order_ne def test_from_srs(self): - srs1 = SRS('epgs:4326') + srs1 = SRS("epgs:4326") srs2 = SRS(srs1) assert srs1 == srs2 - \ No newline at end of file + + +class Test_0_ProjDefaultDataPath(object): + + def test_known_srs(self): + srs.SRS(4326) + + def test_unknown_srs(self): + with pytest.raises(RuntimeError): + srs.SRS(1234) + + +@pytest.fixture(scope="class") +def custom_proj_data_dir(): + srs._proj_initalized = False + srs._srs_cache = {} + base_config().srs.proj_data_dir = os.path.dirname(__file__) + + yield + + srs._proj_initalized = False + srs._srs_cache = {} + srs.set_datapath(None) + base_config().srs.proj_data_dir = None + +@pytest.mark.usefixtures("custom_proj_data_dir") +class Test_1_ProjDataPath(object): + + def test_dummy_srs(self): + srs.SRS(1234) + + def test_unknown_srs(self): + with pytest.raises(RuntimeError): + srs.SRS(2339) + +class TestPreferredSrcSRS(object): + + # test selection of preferred SRS + # unprojected: 4326, 4258 + # projected: 3857, 25831, 25832, 31467 + @pytest.mark.parametrize("target,available,expected", [ + + # always return target if available + (4326, [25832, 4326, 3857], 4326), + + (4326, [25832, 4326, 3857], 4326), + (4326, [25831, 3857], 3857), + + (3857, [25832, 4258, 3857, 31466], 3857), + (3857, [25832, 4258, 31467, 25831, 31466], 25832), + (3857, [4258, 31467, 25831, 31466], 25831), + (3857, [4258, 31467, 31466], 31467), + (3857, [4258, 31466], 31466), + (3857, [4258], 4258), + + # always return first preferred, regardless of order in available + (4326, [3857, 4258], 4258), + (4326, [4258, 3857], 4258), + + # no preferred, return first that is also projected/unprojected + (31467, [4326, 25831, 3857], 25831), + (4258, [25831, 4326, 3857], 4326), + + # no preferred and no srs that is also projected/unprojected, return first + (31467, [4326, 4258], 4326), + (4258, [3857, 25832, 31467], 3857), + ]) + def test_preferred(self, target, available, expected): + preferredSRS = PreferredSrcSRS() + preferredSRS.add(SRS(4326), [SRS(4258), SRS(3857)]) + preferredSRS.add(SRS(3857), [SRS(25832), SRS(25831), SRS(31467), SRS(4326)]) + + assert preferredSRS.preferred_src(SRS(target), [SRS(c) for c in available]) == SRS(expected) + + def test_no_available(self): + preferredSRS = PreferredSrcSRS() + preferredSRS.add(SRS(4326), [SRS(4258), SRS(3857)]) + + with pytest.raises(ValueError): + preferredSRS.preferred_src(SRS(4326), []) + + +class TestSupportedSRS(object): + @pytest.fixture + def preferred(self): + preferredSRS = PreferredSrcSRS() + preferredSRS.add(SRS(4326), [SRS(4258), SRS(3857)]) + preferredSRS.add(SRS(3857), [SRS(25832), SRS(25831), SRS(31467), SRS(4326)]) + return preferredSRS + + def test_supported(self, preferred): + supported = SupportedSRS([SRS(4326), SRS(25832)], preferred) + assert SRS(4326) in supported + assert SRS(4258) not in supported + assert SRS(25832) in supported + + def test_best_srs(self, preferred): + supported = SupportedSRS([SRS(4326), SRS(25832)], preferred) + assert supported.best_srs(SRS(4326)) == SRS(4326) + assert supported.best_srs(SRS(4258)) == SRS(4326) + assert supported.best_srs(SRS(25832)) == SRS(25832) + assert supported.best_srs(SRS(25831)) == SRS(25832) + assert supported.best_srs(SRS(3857)) == SRS(25832) + supported = SupportedSRS([SRS(4326), SRS(31467), SRS(25831)], preferred) + assert supported.best_srs(SRS(3857)) == SRS(25831) + assert supported.best_srs(SRS(25831)) == SRS(25831) + + def test_best_srs_no_preferred(self, preferred): + supported = SupportedSRS([SRS(4326), SRS(25832)], None) + assert supported.best_srs(SRS(4326)) == SRS(4326) + assert supported.best_srs(SRS(4258)) == SRS(4326) + assert supported.best_srs(SRS(25832)) == SRS(25832) + assert supported.best_srs(SRS(25831)) == SRS(25832) + assert supported.best_srs(SRS(3857)) == SRS(25832) + diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_tiled_source.py mapproxy-1.12.0/mapproxy/test/unit/test_tiled_source.py --- mapproxy-1.11.0/mapproxy/test/unit/test_tiled_source.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_tiled_source.py 2019-08-30 07:34:08.000000000 +0000 @@ -14,45 +14,72 @@ # limitations under the License. -from mapproxy.client.tile import TMSClient +from mapproxy.client.tile import TileClient, TileURLTemplate from mapproxy.grid import TileGrid from mapproxy.srs import SRS from mapproxy.source.tile import TiledSource from mapproxy.source.error import HTTPSourceErrorHandler from mapproxy.layer import MapQuery - from mapproxy.test.http import mock_httpd -from nose.tools import eq_ -TEST_SERVER_ADDRESS = ('127.0.0.1', 56413) -TESTSERVER_URL = 'http://%s:%d' % TEST_SERVER_ADDRESS + +TEST_SERVER_ADDRESS = ("127.0.0.1", 56413) +TESTSERVER_URL = ("http://%s:%d" % TEST_SERVER_ADDRESS) + "/%(tms_path)s.png" + class TestTileClientOnError(object): + def setup(self): self.grid = TileGrid(SRS(4326), bbox=[-180, -90, 180, 90]) - self.client = TMSClient(TESTSERVER_URL) + self.client = TileClient(TileURLTemplate(TESTSERVER_URL)) def test_cacheable_response(self): error_handler = HTTPSourceErrorHandler() error_handler.add_handler(500, (255, 0, 0), cacheable=True) self.source = TiledSource(self.grid, self.client, error_handler=error_handler) - with mock_httpd(TEST_SERVER_ADDRESS, [({'path': '/1/0/0.png'}, - {'body': b'error', 'status': 500, 'headers':{'content-type': 'text/plain'}})]): - resp = self.source.get_map(MapQuery([-180, -90, 0, 90], (256, 256), SRS(4326), format='png')) + with mock_httpd( + TEST_SERVER_ADDRESS, + [ + ( + {"path": "/1/0/0.png"}, + { + "body": b"error", + "status": 500, + "headers": {"content-type": "text/plain"}, + }, + ) + ], + ): + resp = self.source.get_map( + MapQuery([-180, -90, 0, 90], (256, 256), SRS(4326), format="png") + ) assert resp.cacheable - eq_(resp.as_image().getcolors(), [((256*256), (255, 0, 0))]) + assert resp.as_image().getcolors() == [((256 * 256), (255, 0, 0))] def test_image_response(self): error_handler = HTTPSourceErrorHandler() error_handler.add_handler(500, (255, 0, 0), cacheable=False) self.source = TiledSource(self.grid, self.client, error_handler=error_handler) - with mock_httpd(TEST_SERVER_ADDRESS, [({'path': '/1/0/0.png'}, - {'body': b'error', 'status': 500, 'headers':{'content-type': 'text/plain'}})]): - resp = self.source.get_map(MapQuery([-180, -90, 0, 90], (256, 256), SRS(4326), format='png')) + with mock_httpd( + TEST_SERVER_ADDRESS, + [ + ( + {"path": "/1/0/0.png"}, + { + "body": b"error", + "status": 500, + "headers": {"content-type": "text/plain"}, + }, + ) + ], + ): + resp = self.source.get_map( + MapQuery([-180, -90, 0, 90], (256, 256), SRS(4326), format="png") + ) assert not resp.cacheable - eq_(resp.as_image().getcolors(), [((256*256), (255, 0, 0))]) + assert resp.as_image().getcolors() == [((256 * 256), (255, 0, 0))] def test_multiple_image_responses(self): error_handler = HTTPSourceErrorHandler() @@ -60,14 +87,36 @@ error_handler.add_handler(204, (255, 0, 127, 200), cacheable=True) self.source = TiledSource(self.grid, self.client, error_handler=error_handler) - with mock_httpd(TEST_SERVER_ADDRESS, [ - ({'path': '/1/0/0.png'}, {'body': b'error', 'status': 500, 'headers':{'content-type': 'text/plain'}}), - ({'path': '/1/0/0.png'}, {'body': b'error', 'status': 204, 'headers':{'content-type': 'text/plain'}})]): - - resp = self.source.get_map(MapQuery([-180, -90, 0, 90], (256, 256), SRS(4326), format='png')) + with mock_httpd( + TEST_SERVER_ADDRESS, + [ + ( + {"path": "/1/0/0.png"}, + { + "body": b"error", + "status": 500, + "headers": {"content-type": "text/plain"}, + }, + ), + ( + {"path": "/1/0/0.png"}, + { + "body": b"error", + "status": 204, + "headers": {"content-type": "text/plain"}, + }, + ), + ], + ): + + resp = self.source.get_map( + MapQuery([-180, -90, 0, 90], (256, 256), SRS(4326), format="png") + ) assert not resp.cacheable - eq_(resp.as_image().getcolors(), [((256*256), (255, 0, 0))]) + assert resp.as_image().getcolors() == [((256 * 256), (255, 0, 0))] - resp = self.source.get_map(MapQuery([-180, -90, 0, 90], (256, 256), SRS(4326), format='png')) + resp = self.source.get_map( + MapQuery([-180, -90, 0, 90], (256, 256), SRS(4326), format="png") + ) assert resp.cacheable - eq_(resp.as_image().getcolors(), [((256*256), (255, 0, 127, 200))]) + assert resp.as_image().getcolors() == [((256 * 256), (255, 0, 127, 200))] diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_tilefilter.py mapproxy-1.12.0/mapproxy/test/unit/test_tilefilter.py --- mapproxy-1.11.0/mapproxy/test/unit/test_tilefilter.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_tilefilter.py 2019-08-30 07:34:08.000000000 +0000 @@ -1,12 +1,12 @@ # This file is part of the MapProxy project. # Copyright (C) 2010, 2011 Omniscale <http://omniscale.de> -# +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at -# +# # http://www.apache.org/licenses/LICENSE-2.0 -# +# # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,17 +14,18 @@ # limitations under the License. from mapproxy.tilefilter import tile_watermark_placement + + def test_tile_watermark_placement(): - from nose.tools import eq_ - eq_(tile_watermark_placement((0, 0, 0)), 'c') - eq_(tile_watermark_placement((1, 0, 0)), 'c') - eq_(tile_watermark_placement((0, 1, 0)), 'b') - eq_(tile_watermark_placement((1, 1, 0)), 'b') - - eq_(tile_watermark_placement((0, 0, 0), True), None) - eq_(tile_watermark_placement((1, 0, 0), True), 'c') - eq_(tile_watermark_placement((2, 0, 0), True), None) + assert tile_watermark_placement((0, 0, 0)) == "c" + assert tile_watermark_placement((1, 0, 0)) == "c" + assert tile_watermark_placement((0, 1, 0)) == "b" + assert tile_watermark_placement((1, 1, 0)) == "b" + + assert tile_watermark_placement((0, 0, 0), True) == None + assert tile_watermark_placement((1, 0, 0), True) == "c" + assert tile_watermark_placement((2, 0, 0), True) == None - eq_(tile_watermark_placement((0, 1, 0), True), 'c') - eq_(tile_watermark_placement((1, 1, 0), True), None) - eq_(tile_watermark_placement((2, 1, 0), True), 'c') + assert tile_watermark_placement((0, 1, 0), True) == "c" + assert tile_watermark_placement((1, 1, 0), True) == None + assert tile_watermark_placement((2, 1, 0), True) == "c" diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_times.py mapproxy-1.12.0/mapproxy/test/unit/test_times.py --- mapproxy-1.11.0/mapproxy/test/unit/test_times.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_times.py 2019-08-30 07:34:08.000000000 +0000 @@ -1,13 +1,25 @@ +# This file is part of the MapProxy project. +# Copyright (C) 2010, 2011 Omniscale <http://omniscale.de> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. from mapproxy.util.times import timestamp_from_isodate +import pytest + def test_timestamp_from_isodate(): - ts = timestamp_from_isodate('2009-06-09T10:57:00') + ts = timestamp_from_isodate("2009-06-09T10:57:00") assert (1244537820.0 - 14 * 3600) < ts < (1244537820.0 + 14 * 3600) - try: - timestamp_from_isodate('2009-06-09T10:57') - except ValueError: - pass - else: - assert False, 'expected ValueError' + with pytest.raises(ValueError): + timestamp_from_isodate("2009-06-09T10:57") diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_timeutils.py mapproxy-1.12.0/mapproxy/test/unit/test_timeutils.py --- mapproxy-1.11.0/mapproxy/test/unit/test_timeutils.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_timeutils.py 2019-08-30 07:34:08.000000000 +0000 @@ -15,36 +15,36 @@ from datetime import datetime from mapproxy.util.times import parse_httpdate, format_httpdate, timestamp -from nose.tools import eq_, raises + +import pytest + class TestHTTPDate(object): + def test_parse_httpdate(self): for date in ( - 'Fri, 13 Feb 2009 23:31:30 GMT', - 'Friday, 13-Feb-09 23:31:30 GMT', - 'Fri Feb 13 23:31:30 2009', - ): - eq_(parse_httpdate(date), 1234567890) + "Fri, 13 Feb 2009 23:31:30 GMT", + "Friday, 13-Feb-09 23:31:30 GMT", + "Fri Feb 13 23:31:30 2009", + ): + assert parse_httpdate(date) == 1234567890 def test_parse_invalid(self): - for date in ( - None, - 'foobar', - '4823764923', - 'Fri, 13 Foo 2009 23:31:30 GMT' - ): - eq_(parse_httpdate(date), None) + for date in (None, "foobar", "4823764923", "Fri, 13 Foo 2009 23:31:30 GMT"): + assert parse_httpdate(date) == None def test_format_httpdate(self): - eq_(format_httpdate(datetime.fromtimestamp(1234567890)), - 'Fri, 13 Feb 2009 23:31:30 GMT') - eq_(format_httpdate(1234567890), - 'Fri, 13 Feb 2009 23:31:30 GMT') + assert ( + format_httpdate(datetime.fromtimestamp(1234567890)) + == "Fri, 13 Feb 2009 23:31:30 GMT" + ) + assert format_httpdate(1234567890) == "Fri, 13 Feb 2009 23:31:30 GMT" - @raises(AssertionError) def test_format_invalid(self): - format_httpdate('foobar') + with pytest.raises(AssertionError): + format_httpdate("foobar") + def test_timestamp(): - eq_(timestamp(1234567890), 1234567890) - eq_(timestamp(datetime.fromtimestamp(1234567890)), 1234567890) + assert timestamp(1234567890) == 1234567890 + assert timestamp(datetime.fromtimestamp(1234567890)) == 1234567890 diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_util_conf_utils.py mapproxy-1.12.0/mapproxy/test/unit/test_util_conf_utils.py --- mapproxy-1.11.0/mapproxy/test/unit/test_util_conf_utils.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_util_conf_utils.py 2019-08-30 07:34:08.000000000 +0000 @@ -14,56 +14,62 @@ # See the License for the specific language governing permissions and # limitations under the License. -from mapproxy.script.conf.utils import update_config - from copy import deepcopy -from nose.tools import eq_ + +from mapproxy.script.conf.utils import update_config class TestUpdateConfig(object): + def test_empty(self): - a = {'a': 'foo', 'b': 42} + a = {"a": "foo", "b": 42} b = {} - eq_(update_config(deepcopy(a), b), a) + assert update_config(deepcopy(a), b) == a def test_add(self): - a = {'a': 'foo', 'b': 42} - b = {'c': [1, 2, 3]} - eq_(update_config(a, b), - {'a': 'foo', 'b': 42, 'c': [1, 2, 3]}) + a = {"a": "foo", "b": 42} + b = {"c": [1, 2, 3]} + assert update_config(a, b) == {"a": "foo", "b": 42, "c": [1, 2, 3]} def test_mod(self): - a = {'a': 'foo', 'b': 42, 'c': {}} - b = {'a': [1, 2, 3], 'c': 1} - eq_(update_config(a, b), - {'b': 42, 'a': [1, 2, 3], 'c': 1}) + a = {"a": "foo", "b": 42, "c": {}} + b = {"a": [1, 2, 3], "c": 1} + assert update_config(a, b) == {"b": 42, "a": [1, 2, 3], "c": 1} def test_nested_add_mod(self): - a = {'a': 'foo', 'b': {'ba': 42, 'bb': {}}} - b = {'b': {'bb': {'bba': 1}, 'bc': [1, 2, 3]}} - eq_(update_config(a, b), - {'a': 'foo', 'b': {'ba': 42, 'bb': {'bba': 1}, 'bc': [1, 2, 3]}}) + a = {"a": "foo", "b": {"ba": 42, "bb": {}}} + b = {"b": {"bb": {"bba": 1}, "bc": [1, 2, 3]}} + assert update_config(a, b) == { + "a": "foo", + "b": {"ba": 42, "bb": {"bba": 1}, "bc": [1, 2, 3]}, + } def test_add_all(self): - a = {'a': 'foo', 'b': {'ba': 42, 'bb': {}}} - b = {'__all__': {'ba': 1}} - eq_(update_config(a, b), - {'a': {'ba': 1}, 'b': {'ba': 1, 'bb': {}}}) + a = {"a": "foo", "b": {"ba": 42, "bb": {}}} + b = {"__all__": {"ba": 1}} + assert update_config(a, b) == {"a": {"ba": 1}, "b": {"ba": 1, "bb": {}}} def test_extend(self): - a = {'a': 'foo', 'b': ['ba']} - b = {'b__extend__': ['bb', 'bc']} - eq_(update_config(a, b), - {'a': 'foo', 'b': ['ba', 'bb', 'bc']}) + a = {"a": "foo", "b": ["ba"]} + b = {"b__extend__": ["bb", "bc"]} + assert update_config(a, b) == {"a": "foo", "b": ["ba", "bb", "bc"]} def test_prefix_wildcard(self): - a = {'test_foo': 'foo', 'test_bar': 'ba', 'test2_foo': 'test2', 'nounderfoo': 1} - b = {'____foo': 42} - eq_(update_config(a, b), - {'test_foo': 42, 'test_bar': 'ba', 'test2_foo': 42, 'nounderfoo': 1}) + a = {"test_foo": "foo", "test_bar": "ba", "test2_foo": "test2", "nounderfoo": 1} + b = {"____foo": 42} + assert update_config(a, b) == { + "test_foo": 42, + "test_bar": "ba", + "test2_foo": 42, + "nounderfoo": 1, + } def test_suffix_wildcard(self): - a = {'test_foo': 'foo', 'test_bar': 'ba', 'test2_foo': 'test2', 'nounderfoo': 1} - b = {'test____': 42} - eq_(update_config(a, b), - {'test_foo': 42, 'test_bar': 42, 'test2_foo': 'test2', 'nounderfoo': 1}) + a = {"test_foo": "foo", "test_bar": "ba", "test2_foo": "test2", "nounderfoo": 1} + b = {"test____": 42} + assert update_config(a, b) == { + "test_foo": 42, + "test_bar": 42, + "test2_foo": "test2", + "nounderfoo": 1, + } diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_utils.py mapproxy-1.12.0/mapproxy/test/unit/test_utils.py --- mapproxy-1.11.0/mapproxy/test/unit/test_utils.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_utils.py 2019-08-30 07:34:08.000000000 +0000 @@ -13,21 +13,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os import glob -import sys +import multiprocessing +import os +import random import shutil +import sys import tempfile import threading -import multiprocessing -import random import time -from mapproxy.util.lock import ( - FileLock, - SemLock, - cleanup_lockdir, - LockTimeout, -) + +from mapproxy.util.lock import FileLock, SemLock, cleanup_lockdir, LockTimeout from mapproxy.util.fs import ( _force_rename_dir, swap_dir, @@ -38,30 +34,35 @@ from mapproxy.util.times import timestamp_before from mapproxy.test.helper import Mocker -from nose.tools import eq_ -is_win = sys.platform == 'win32' +is_win = sys.platform == "win32" + class TestFileLock(Mocker): + def setup(self): Mocker.setup(self) self.lock_dir = tempfile.mkdtemp() - self.lock_file = os.path.join(self.lock_dir, 'lock.lck') + self.lock_file = os.path.join(self.lock_dir, "lock.lck") + def teardown(self): shutil.rmtree(self.lock_dir) Mocker.teardown(self) + def test_file_lock_timeout(self): lock = self._create_lock() assert_locked(self.lock_file) - lock # prevent lint warnings + lock # prevent lint warnings def test_file_lock(self): # Test a lock that becomes free during a waiting lock() call. class Lock(threading.Thread): + def __init__(self, lock_file): threading.Thread.__init__(self) self.lock_file = lock_file self.lock = FileLock(self.lock_file) + def run(self): self.lock.lock() time.sleep(0.2) @@ -83,19 +84,19 @@ l.lock() locked_for = time.time() - start_time - assert locked_for - 0.2 <=0.1, 'locking took to long?! (rerun if not sure)' + assert locked_for - 0.2 <= 0.1, "locking took to long?! (rerun if not sure)" - #cleanup + # cleanup l.unlock() lock_thread.join() def test_lock_cleanup(self): - old_lock_file = os.path.join(self.lock_dir, 'lock_old.lck') + old_lock_file = os.path.join(self.lock_dir, "lock_old.lck") l = FileLock(old_lock_file) l.lock() l.unlock() mtime = os.stat(old_lock_file).st_mtime - mtime -= 7*60 + mtime -= 7 * 60 os.utime(old_lock_file, (mtime, mtime)) l = self._create_lock() @@ -108,26 +109,27 @@ assert os.path.exists(self.lock_file) def test_concurrent_access(self): - count_file = os.path.join(self.lock_dir, 'count.txt') - with open(count_file, 'wb') as f: - f.write(b'0') + count_file = os.path.join(self.lock_dir, "count.txt") + with open(count_file, "wb") as f: + f.write(b"0") def count_up(): with FileLock(self.lock_file, timeout=60): - with open(count_file, 'r+b') as f: + with open(count_file, "r+b") as f: counter = int(f.read().strip()) f.seek(0) - f.write(str(counter+1).encode('utf-8')) + f.write(str(counter + 1).encode("utf-8")) def do_it(): for x in range(20): time.sleep(0.002) count_up() + threads = [threading.Thread(target=do_it) for _ in range(20)] [t.start() for t in threads] [t.join() for t in threads] - with open(count_file, 'r+b') as f: + with open(count_file, "r+b") as f: counter = int(f.read().strip()) assert counter == 400, counter @@ -137,41 +139,50 @@ l.lock() assert os.path.exists(self.lock_file) l.unlock() - assert not os.path.exists(self.lock_file) - - l.lock() - assert os.path.exists(self.lock_file) - os.remove(self.lock_file) - assert not os.path.exists(self.lock_file) - # ignore removed lock - l.unlock() - assert not os.path.exists(self.lock_file) + if is_win: # not removed on windows + assert os.path.exists(self.lock_file) + else: + assert not os.path.exists(self.lock_file) + if is_win: + # not possible to remove lock file when lock is held + pass + else: + l.lock() + assert os.path.exists(self.lock_file) + os.remove(self.lock_file) + assert not os.path.exists(self.lock_file) + # ignore removed lock + l.unlock() + assert not os.path.exists(self.lock_file) def _create_lock(self): lock = FileLock(self.lock_file) lock.lock() return lock + def assert_locked(lock_file, timeout=0.02, step=0.001): assert os.path.exists(lock_file) l = FileLock(lock_file, timeout=timeout, step=step) try: l.lock() - assert False, 'file was not locked' + assert False, "file was not locked" except LockTimeout: pass class TestSemLock(object): + def setup(self): self.lock_dir = tempfile.mkdtemp() - self.lock_file = os.path.join(self.lock_dir, 'lock.lck') + self.lock_file = os.path.join(self.lock_dir, "lock.lck") + def teardown(self): shutil.rmtree(self.lock_dir) def count_lockfiles(self): - return len(glob.glob(self.lock_file + '*')) + return len(glob.glob(self.lock_file + "*")) def test_single(self): locks = [SemLock(self.lock_file, 1, timeout=0.01) for _ in range(2)] @@ -181,17 +192,16 @@ except LockTimeout: pass else: - assert False, 'expected LockTimeout' - + assert False, "expected LockTimeout" def test_creating(self): locks = [SemLock(self.lock_file, 2) for _ in range(3)] - eq_(self.count_lockfiles(), 0) + assert self.count_lockfiles() == 0 locks[0].lock() - eq_(self.count_lockfiles(), 1) + assert self.count_lockfiles() == 1 locks[1].lock() - eq_(self.count_lockfiles(), 2) + assert self.count_lockfiles() == 2 assert os.path.exists(locks[0]._lock._path) assert os.path.exists(locks[1]._lock._path) locks[0].unlock() @@ -202,17 +212,17 @@ def test_timeout(self): locks = [SemLock(self.lock_file, 2, timeout=0.1) for _ in range(3)] - eq_(self.count_lockfiles(), 0) + assert self.count_lockfiles() == 0 locks[0].lock() - eq_(self.count_lockfiles(), 1) + assert self.count_lockfiles() == 1 locks[1].lock() - eq_(self.count_lockfiles(), 2) + assert self.count_lockfiles() == 2 try: locks[2].lock() except LockTimeout: pass else: - assert False, 'expected LockTimeout' + assert False, "expected LockTimeout" locks[0].unlock() locks[2].unlock() @@ -227,88 +237,96 @@ old_locks = random.sample([l for l in locks if l._locked], 3) for l in old_locks: l.unlock() - eq_(len([l for l in locks if l._locked]), 2) - eq_(len([l for l in locks if not l._locked]), 18) + assert len([l for l in locks if l._locked]) == 2 + assert len([l for l in locks if not l._locked]) == 18 new_locks = random.sample([l for l in locks if not l._locked], 3) for l in new_locks: l.lock() - eq_(len([l for l in locks if l._locked]), 5) - eq_(len([l for l in locks if not l._locked]), 15) + assert len([l for l in locks if l._locked]) == 5 + assert len([l for l in locks if not l._locked]) == 15 assert self.count_lockfiles() == 8 class DirTest(object): + def setup(self): self.tmpdir = tempfile.mkdtemp() + def teardown(self): if os.path.exists(self.tmpdir): shutil.rmtree(self.tmpdir) + def mkdir(self, name): dirname = os.path.join(self.tmpdir, name) os.mkdir(dirname) self.mkfile(name, dirname=dirname) return dirname + def mkfile(self, name, dirname=None): if dirname is None: dirname = self.mkdir(name) - filename = os.path.join(dirname, name + '.txt') - open(filename, 'wb').close() + filename = os.path.join(dirname, name + ".txt") + open(filename, "wb").close() return filename class TestForceRenameDir(DirTest): + def test_rename(self): - src_dir = self.mkdir('bar') - dst_dir = os.path.join(self.tmpdir, 'baz') + src_dir = self.mkdir("bar") + dst_dir = os.path.join(self.tmpdir, "baz") _force_rename_dir(src_dir, dst_dir) assert os.path.exists(dst_dir) - assert os.path.exists(os.path.join(dst_dir, 'bar.txt')) + assert os.path.exists(os.path.join(dst_dir, "bar.txt")) assert not os.path.exists(src_dir) + def test_rename_overwrite(self): - src_dir = self.mkdir('bar') - dst_dir = self.mkdir('baz') + src_dir = self.mkdir("bar") + dst_dir = self.mkdir("baz") _force_rename_dir(src_dir, dst_dir) assert os.path.exists(dst_dir) - assert os.path.exists(os.path.join(dst_dir, 'bar.txt')) + assert os.path.exists(os.path.join(dst_dir, "bar.txt")) assert not os.path.exists(src_dir) class TestSwapDir(DirTest): + def test_swap_dir(self): - src_dir = self.mkdir('bar') - dst_dir = os.path.join(self.tmpdir, 'baz') + src_dir = self.mkdir("bar") + dst_dir = os.path.join(self.tmpdir, "baz") swap_dir(src_dir, dst_dir) assert os.path.exists(dst_dir) - assert os.path.exists(os.path.join(dst_dir, 'bar.txt')) + assert os.path.exists(os.path.join(dst_dir, "bar.txt")) assert not os.path.exists(src_dir) def test_swap_dir_w_old(self): - src_dir = self.mkdir('bar') - dst_dir = self.mkdir('baz') + src_dir = self.mkdir("bar") + dst_dir = self.mkdir("baz") swap_dir(src_dir, dst_dir) assert os.path.exists(dst_dir) - assert os.path.exists(os.path.join(dst_dir, 'bar.txt')) + assert os.path.exists(os.path.join(dst_dir, "bar.txt")) assert not os.path.exists(src_dir) def test_swap_dir_keep_old(self): - src_dir = self.mkdir('bar') - dst_dir = self.mkdir('baz') + src_dir = self.mkdir("bar") + dst_dir = self.mkdir("baz") - swap_dir(src_dir, dst_dir, keep_old=True, backup_ext='.bak') + swap_dir(src_dir, dst_dir, keep_old=True, backup_ext=".bak") assert os.path.exists(dst_dir) - assert os.path.exists(os.path.join(dst_dir, 'bar.txt')) - assert os.path.exists(dst_dir + '.bak') - assert os.path.exists(os.path.join(dst_dir + '.bak', 'baz.txt')) + assert os.path.exists(os.path.join(dst_dir, "bar.txt")) + assert os.path.exists(dst_dir + ".bak") + assert os.path.exists(os.path.join(dst_dir + ".bak", "baz.txt")) class TestCleanupDirectory(DirTest): + def test_no_remove(self): - dirs = [self.mkdir('dir'+str(n)) for n in range(10)] + dirs = [self.mkdir("dir" + str(n)) for n in range(10)] for d in dirs: assert os.path.exists(d), d cleanup_directory(self.tmpdir, timestamp_before(minutes=1)) @@ -318,11 +336,13 @@ def test_file_handler(self): files = [] file_handler_calls = [] + def file_handler(filename): file_handler_calls.append(filename) + new_date = timestamp_before(weeks=1) for n in range(10): - fname = 'foo'+str(n) + fname = "foo" + str(n) filename = self.mkfile(fname) os.utime(filename, (new_date, new_date)) files.append(filename) @@ -336,14 +356,14 @@ assert set(files) == set(file_handler_calls) def test_no_directory(self): - cleanup_directory(os.path.join(self.tmpdir, 'invalid'), timestamp_before()) + cleanup_directory(os.path.join(self.tmpdir, "invalid"), timestamp_before()) # nothing should happen def test_remove_all(self): files = [] new_date = timestamp_before(weeks=1) for n in range(10): - fname = 'foo'+str(n) + fname = "foo" + str(n) filename = self.mkfile(fname) os.utime(filename, (new_date, new_date)) files.append(filename) @@ -355,18 +375,17 @@ assert not os.path.exists(filename), filename assert not os.path.exists(os.path.dirname(filename)), filename - def test_remove_empty_dirs(self): - os.makedirs(os.path.join(self.tmpdir, 'foo', 'bar', 'baz')) + os.makedirs(os.path.join(self.tmpdir, "foo", "bar", "baz")) cleanup_directory(self.tmpdir, timestamp_before(minutes=-1)) - assert not os.path.exists(os.path.join(self.tmpdir, 'foo')) + assert not os.path.exists(os.path.join(self.tmpdir, "foo")) def test_remove_some(self): files = [] # create a few files, every other file is one week old new_date = timestamp_before(weeks=1) for n in range(10): - fname = 'foo'+str(n) + fname = "foo" + str(n) filename = self.mkfile(fname) if n % 2 == 0: os.utime(filename, (new_date, new_date)) @@ -388,13 +407,16 @@ for filename in files[1::2]: assert os.path.exists(filename), filename + def _write_atomic_data(i_filename): (i, filename) = i_filename - data = str(i) + '\n' + 'x' * 10000 - write_atomic(filename, data.encode('utf-8')) + data = str(i) + "\n" + "x" * 10000 + write_atomic(filename, data.encode("utf-8")) time.sleep(0.001) + class TestWriteAtomic(object): + def setup(self): self.dirname = tempfile.mkdtemp() @@ -403,7 +425,7 @@ shutil.rmtree(self.dirname) def test_concurrent_write(self): - filename = os.path.join(self.dirname, 'tmpfile') + filename = os.path.join(self.dirname, "tmpfile") num_writes = 800 concurrent_writes = 8 @@ -414,8 +436,9 @@ p.join() assert os.path.exists(filename) - last_i = int(open(filename).readline()) - assert last_i > (num_writes / 2), ("file should contain content from " + last_i = int(open(filename).readline()) + assert last_i > (num_writes / 2), ( + "file should contain content from " "later writes, got content from write %d" % (last_i + 1) ) os.unlink(filename) @@ -423,18 +446,19 @@ def test_not_a_file(self): # check that expected errors are not hidden - filename = os.path.join(self.dirname, 'tmpfile') + filename = os.path.join(self.dirname, "tmpfile") os.mkdir(filename) try: - write_atomic(filename, b'12345') - except OSError: + write_atomic(filename, b"12345") + except (OSError, IOError): pass else: - assert False, 'expected exception' + assert False, "expected exception" def test_reraise_exception(): + def valueerror_raiser(): raise ValueError() @@ -449,4 +473,4 @@ except TypeError as ex: assert ex else: - assert False, 'expected exception' \ No newline at end of file + assert False, "expected exception" diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_wms_capabilities.py mapproxy-1.12.0/mapproxy/test/unit/test_wms_capabilities.py --- mapproxy-1.11.0/mapproxy/test/unit/test_wms_capabilities.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_wms_capabilities.py 2019-08-30 07:34:08.000000000 +0000 @@ -17,26 +17,28 @@ from mapproxy.layer import DefaultMapExtent, MapExtent from mapproxy.srs import SRS -from nose.tools import eq_ class TestLimitSRSExtents(object): + def test_defaults(self): - eq_( - limit_srs_extents({}, ['EPSG:4326', 'EPSG:3857']), - { - 'EPSG:4326': DefaultMapExtent(), - 'EPSG:3857': DefaultMapExtent(), - } - ) + assert limit_srs_extents({}, ["EPSG:4326", "EPSG:3857"]) == { + "EPSG:4326": DefaultMapExtent(), + "EPSG:3857": DefaultMapExtent(), + } + def test_unsupported(self): - eq_( - limit_srs_extents({'EPSG:9999': DefaultMapExtent()}, - ['EPSG:4326', 'EPSG:3857']), - {} + assert ( + limit_srs_extents( + {"EPSG:9999": DefaultMapExtent()}, ["EPSG:4326", "EPSG:3857"] + ) + == {} ) + def test_limited_unsupported(self): - eq_( - limit_srs_extents({'EPSG:9999': DefaultMapExtent(), 'EPSG:4326': MapExtent([0, 0, 10, 10], SRS(4326))}, - ['EPSG:4326', 'EPSG:3857']), - {'EPSG:4326': MapExtent([0, 0, 10, 10], SRS(4326)),} - ) + assert limit_srs_extents( + { + "EPSG:9999": DefaultMapExtent(), + "EPSG:4326": MapExtent([0, 0, 10, 10], SRS(4326)), + }, + ["EPSG:4326", "EPSG:3857"], + ) == {"EPSG:4326": MapExtent([0, 0, 10, 10], SRS(4326))} diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_wms_layer.py mapproxy-1.12.0/mapproxy/test/unit/test_wms_layer.py --- mapproxy-1.11.0/mapproxy/test/unit/test_wms_layer.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_wms_layer.py 2019-08-30 07:34:08.000000000 +0000 @@ -18,7 +18,6 @@ from mapproxy.layer import MapQuery, InfoQuery from mapproxy.srs import SRS from mapproxy.service.wms import combined_layers -from nose.tools import eq_ from mapproxy.source.wms import WMSSource from mapproxy.client.wms import WMSClient from mapproxy.request.wms import create_request @@ -28,40 +27,68 @@ q = MapQuery((0, 0, 10000, 10000), (100, 100), SRS(3857)) def test_empty(self): - eq_(combined_layers([], self.q), []) + assert combined_layers([], self.q) == [] def test_same_source(self): layers = [ - WMSSource(WMSClient(create_request({'url': 'http://foo/', 'layers': 'a'}, {}))), - WMSSource(WMSClient(create_request({'url': 'http://foo/', 'layers': 'b'}, {}))), + WMSSource( + WMSClient(create_request({"url": "http://foo/", "layers": "a"}, {})) + ), + WMSSource( + WMSClient(create_request({"url": "http://foo/", "layers": "b"}, {})) + ), ] combined = combined_layers(layers, self.q) - eq_(len(combined), 1) - eq_(combined[0].client.request_template.params.layers, ['a', 'b']) + assert len(combined) == 1 + assert combined[0].client.request_template.params.layers == ["a", "b"] def test_mixed_hosts(self): layers = [ - WMSSource(WMSClient(create_request({'url': 'http://foo/', 'layers': 'a'}, {}))), - WMSSource(WMSClient(create_request({'url': 'http://foo/', 'layers': 'b'}, {}))), - WMSSource(WMSClient(create_request({'url': 'http://bar/', 'layers': 'c'}, {}))), - WMSSource(WMSClient(create_request({'url': 'http://bar/', 'layers': 'd'}, {}))), - WMSSource(WMSClient(create_request({'url': 'http://foo/', 'layers': 'e'}, {}))), - WMSSource(WMSClient(create_request({'url': 'http://foo/', 'layers': 'f'}, {}))), + WMSSource( + WMSClient(create_request({"url": "http://foo/", "layers": "a"}, {})) + ), + WMSSource( + WMSClient(create_request({"url": "http://foo/", "layers": "b"}, {})) + ), + WMSSource( + WMSClient(create_request({"url": "http://bar/", "layers": "c"}, {})) + ), + WMSSource( + WMSClient(create_request({"url": "http://bar/", "layers": "d"}, {})) + ), + WMSSource( + WMSClient(create_request({"url": "http://foo/", "layers": "e"}, {})) + ), + WMSSource( + WMSClient(create_request({"url": "http://foo/", "layers": "f"}, {})) + ), ] combined = combined_layers(layers, self.q) - eq_(len(combined), 3) - eq_(combined[0].client.request_template.params.layers, ['a', 'b']) - eq_(combined[1].client.request_template.params.layers, ['c', 'd']) - eq_(combined[2].client.request_template.params.layers, ['e', 'f']) + assert len(combined) == 3 + assert combined[0].client.request_template.params.layers == ["a", "b"] + assert combined[1].client.request_template.params.layers == ["c", "d"] + assert combined[2].client.request_template.params.layers == ["e", "f"] def test_mixed_params(self): layers = [ - WMSSource(WMSClient(create_request({'url': 'http://foo/', 'layers': 'a'}, {}))), - WMSSource(WMSClient(create_request({'url': 'http://foo/', 'layers': 'b'}, {}))), - WMSSource(WMSClient(create_request({'url': 'http://foo/', 'layers': 'c'}, {}))), - WMSSource(WMSClient(create_request({'url': 'http://foo/', 'layers': 'd'}, {}))), - WMSSource(WMSClient(create_request({'url': 'http://foo/', 'layers': 'e'}, {}))), - WMSSource(WMSClient(create_request({'url': 'http://foo/', 'layers': 'f'}, {}))), + WMSSource( + WMSClient(create_request({"url": "http://foo/", "layers": "a"}, {})) + ), + WMSSource( + WMSClient(create_request({"url": "http://foo/", "layers": "b"}, {})) + ), + WMSSource( + WMSClient(create_request({"url": "http://foo/", "layers": "c"}, {})) + ), + WMSSource( + WMSClient(create_request({"url": "http://foo/", "layers": "d"}, {})) + ), + WMSSource( + WMSClient(create_request({"url": "http://foo/", "layers": "e"}, {})) + ), + WMSSource( + WMSClient(create_request({"url": "http://foo/", "layers": "f"}, {})) + ), ] layers[0].supported_srs = ["EPSG:4326"] @@ -71,14 +98,16 @@ layers[3].supported_formats = ["image/png"] combined = combined_layers(layers, self.q) - eq_(len(combined), 3) - eq_(combined[0].client.request_template.params.layers, ['a', 'b']) - eq_(combined[1].client.request_template.params.layers, ['c', 'd']) - eq_(combined[2].client.request_template.params.layers, ['e', 'f']) + assert len(combined) == 3 + assert combined[0].client.request_template.params.layers == ["a", "b"] + assert combined[1].client.request_template.params.layers == ["c", "d"] + assert combined[2].client.request_template.params.layers == ["e", "f"] class TestInfoQuery(object): + def test_coord(self): - query = InfoQuery((8, 50, 9, 51), (400, 1000), - SRS(4326), (100, 600), 'text/plain') - eq_(query.coord, (8.25, 50.4)) + query = InfoQuery( + (8, 50, 9, 51), (400, 1000), SRS(4326), (100, 600), "text/plain" + ) + assert query.coord == (8.25, 50.4) diff -Nru mapproxy-1.11.0/mapproxy/test/unit/test_yaml.py mapproxy-1.12.0/mapproxy/test/unit/test_yaml.py --- mapproxy-1.11.0/mapproxy/test/unit/test_yaml.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/test/unit/test_yaml.py 2019-08-30 07:34:08.000000000 +0000 @@ -18,10 +18,10 @@ from mapproxy.util.yaml import load_yaml, load_yaml_file, YAMLError from mapproxy.compat import string_type -from nose.tools import eq_ class TestLoadYAMLFile(object): + def setup(self): self.tmp_files = [] @@ -31,7 +31,7 @@ def yaml_file(self, content): fd, fname = tempfile.mkstemp() - f = os.fdopen(fd, 'w') + f = os.fdopen(fd, "w") f.write(content) self.tmp_files.append(fname) return fname @@ -39,23 +39,31 @@ def test_load_yaml_file(self): f = self.yaml_file("hello:\n - 1\n - 2") doc = load_yaml_file(open(f)) - eq_(doc, {'hello': [1, 2]}) + assert doc == {"hello": [1, 2]} def test_load_yaml_file_filename(self): f = self.yaml_file("hello:\n - 1\n - 2") assert isinstance(f, string_type) doc = load_yaml_file(f) - eq_(doc, {'hello': [1, 2]}) + assert doc == {"hello": [1, 2]} def test_load_yaml(self): doc = load_yaml("hello:\n - 1\n - 2") - eq_(doc, {'hello': [1, 2]}) + assert doc == {"hello": [1, 2]} def test_load_yaml_with_tabs(self): try: f = self.yaml_file("hello:\n\t- world") load_yaml_file(f) except YAMLError as ex: - assert 'line 2' in str(ex) + assert "line 2" in str(ex) + else: + assert False, "expected YAMLError" + + def test_load_yaml_string_error(self): + try: + load_yaml('only a string') + except YAMLError as ex: + assert "not a YAML dict" in str(ex) else: - assert False, 'expected YAMLError' + assert False, "expected YAMLError" diff -Nru mapproxy-1.11.0/mapproxy/util/async_.py mapproxy-1.12.0/mapproxy/util/async_.py --- mapproxy-1.11.0/mapproxy/util/async_.py 1970-01-01 00:00:00.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/util/async_.py 2019-08-30 07:34:08.000000000 +0000 @@ -0,0 +1,227 @@ +# This file is part of the MapProxy project. +# Copyright (C) 2011 Omniscale <http://omniscale.de> +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +MAX_MAP_ASYNC_THREADS = 20 + +try: + import Queue +except ImportError: + import queue as Queue + +import sys +import threading + +from mapproxy.config import base_config +from mapproxy.config import local_base_config +from mapproxy.compat import PY2 + +import logging +log_system = logging.getLogger('mapproxy.system') + +class AsyncResult(object): + def __init__(self, result=None, exception=None): + self.result = result + self.exception = exception + + def __repr__(self): + return "<AsyncResult result='%s' exception='%s'>" % ( + self.result, self.exception) + + +def _result_iter(results, use_result_objects=False): + for result in results: + if use_result_objects: + exception = None + if (isinstance(result, tuple) and len(result) == 3 and + isinstance(result[1], Exception)): + exception = result + result = None + yield AsyncResult(result, exception) + else: + yield result + + +class ThreadWorker(threading.Thread): + def __init__(self, task_queue, result_queue): + threading.Thread.__init__(self) + self.task_queue = task_queue + self.result_queue = result_queue + self.base_config = base_config() + def run(self): + with local_base_config(self.base_config): + while True: + task = self.task_queue.get() + if task is None: + self.task_queue.task_done() + break + exec_id, func, args = task + try: + result = func(*args) + except Exception: + result = sys.exc_info() + self.result_queue.put((exec_id, result)) + self.task_queue.task_done() + + +def _consume_queue(queue): + """ + Get all items from queue. + """ + while not queue.empty(): + try: + queue.get(block=False) + queue.task_done() + except Queue.Empty: + pass + + +class ThreadPool(object): + def __init__(self, size=4): + self.pool_size = size + self.task_queue = Queue.Queue() + self.result_queue = Queue.Queue() + self.pool = None + def map_each(self, func_args, raise_exceptions): + """ + args should be a list of function arg tuples. + map_each calls each function with the given arg. + """ + if self.pool_size < 2: + for func, arg in func_args: + try: + yield func(*arg) + except Exception: + yield sys.exc_info() + return + + self.pool = self._init_pool() + + i = 0 + for i, (func, arg) in enumerate(func_args): + self.task_queue.put((i, func, arg)) + + results = {} + + next_result = 0 + for value in self._get_results(next_result, results, raise_exceptions): + yield value + next_result += 1 + + self.task_queue.join() + for value in self._get_results(next_result, results, raise_exceptions): + yield value + next_result += 1 + + self.shutdown() + + def _single_call(self, func, args, use_result_objects): + try: + result = func(*args) + except Exception: + if not use_result_objects: + raise + result = sys.exc_info() + return _result_iter([result], use_result_objects) + + def map(self, func, *args, **kw): + return list(self.imap(func, *args, **kw)) + + def imap(self, func, *args, **kw): + use_result_objects = kw.get('use_result_objects', False) + if len(args[0]) == 1: + return self._single_call(func, next(iter(zip(*args))), use_result_objects) + return _result_iter(self.map_each([(func, arg) for arg in zip(*args)], raise_exceptions=not use_result_objects), + use_result_objects) + + def starmap(self, func, args, **kw): + use_result_objects = kw.get('use_result_objects', False) + if len(args[0]) == 1: + return self._single_call(func, args[0], use_result_objects) + + return _result_iter(self.map_each([(func, arg) for arg in args], raise_exceptions=not use_result_objects), + use_result_objects) + + def starcall(self, args, **kw): + def call(func, *args): + return func(*args) + return self.starmap(call, args, **kw) + + def _get_results(self, next_result, results, raise_exceptions): + for i, value in self._fetch_results(raise_exceptions): + if i == next_result: + yield value + next_result += 1 + while next_result in results: + yield results.pop(next_result) + next_result += 1 + else: + results[i] = value + + def _fetch_results(self, raise_exceptions): + while not self.task_queue.empty() or not self.result_queue.empty(): + task_result = self.result_queue.get() + if (raise_exceptions and isinstance(task_result[1], tuple) and + len(task_result[1]) == 3 and + isinstance(task_result[1][1], Exception)): + self.shutdown(force=True) + exc_class, exc, tb = task_result[1] + if PY2: + exec('raise exc_class, exc, tb') + else: + raise exc.with_traceback(tb) + yield task_result + + def shutdown(self, force=False): + """ + Send shutdown sentinel to all executor threads. If `force` is True, + clean task_queue and result_queue. + """ + + if force: + _consume_queue(self.task_queue) + _consume_queue(self.result_queue) + for _ in range(self.pool_size): + self.task_queue.put(None) + + def _init_pool(self): + if self.pool_size < 2: + return [] + pool = [] + for _ in range(self.pool_size): + t = ThreadWorker(self.task_queue, self.result_queue) + t.daemon = True + t.start() + pool.append(t) + return pool + + +def imap(func, *args): + pool = ThreadPool(min(len(args[0]), MAX_MAP_ASYNC_THREADS)) + return pool.imap(func, *args) + +def starmap(func, args): + pool = ThreadPool(min(len(args[0]), MAX_MAP_ASYNC_THREADS)) + return pool.starmap(func, args) + +def starcall(args): + pool = ThreadPool(min(len(args[0]), MAX_MAP_ASYNC_THREADS)) + return pool.starcall(args) + +def run_non_blocking(func, args, kw={}): + return func(*args, **kw) + + +Pool = ThreadPool diff -Nru mapproxy-1.11.0/mapproxy/util/async.py mapproxy-1.12.0/mapproxy/util/async.py --- mapproxy-1.11.0/mapproxy/util/async.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/util/async.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,343 +0,0 @@ -# This file is part of the MapProxy project. -# Copyright (C) 2011 Omniscale <http://omniscale.de> -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -MAX_MAP_ASYNC_THREADS = 20 - -try: - import Queue -except ImportError: - import queue as Queue - -import sys -import threading - -try: - import eventlet - import eventlet.greenpool - import eventlet.tpool - import eventlet.patcher - _has_eventlet = True - - import eventlet.debug - eventlet.debug.hub_exceptions(False) - -except ImportError: - _has_eventlet = False - -from mapproxy.config import base_config -from mapproxy.config import local_base_config -from mapproxy.compat import PY2 - -import logging -log_system = logging.getLogger('mapproxy.system') - -class AsyncResult(object): - def __init__(self, result=None, exception=None): - self.result = result - self.exception = exception - - def __repr__(self): - return "<AsyncResult result='%s' exception='%s'>" % ( - self.result, self.exception) - - -def _result_iter(results, use_result_objects=False): - for result in results: - if use_result_objects: - exception = None - if (isinstance(result, tuple) and len(result) == 3 and - isinstance(result[1], Exception)): - exception = result - result = None - yield AsyncResult(result, exception) - else: - yield result - -class EventletPool(object): - def __init__(self, size=100): - self.size = size - self.base_config = base_config() - - def shutdown(self, force=False): - # there is not way to stop a GreenPool - pass - - def map(self, func, *args, **kw): - return list(self.imap(func, *args, **kw)) - - def imap(self, func, *args, **kw): - use_result_objects = kw.get('use_result_objects', False) - def call(*args): - with local_base_config(self.base_config): - try: - return func(*args) - except Exception: - if use_result_objects: - return sys.exc_info() - else: - raise - if len(args[0]) == 1: - eventlet.sleep() - return _result_iter([call(*list(zip(*args))[0])], use_result_objects) - pool = eventlet.greenpool.GreenPool(self.size) - return _result_iter(pool.imap(call, *args), use_result_objects) - - def starmap(self, func, args, **kw): - use_result_objects = kw.get('use_result_objects', False) - def call(*args): - with local_base_config(self.base_config): - try: - return func(*args) - except Exception: - if use_result_objects: - return sys.exc_info() - else: - raise - if len(args) == 1: - eventlet.sleep() - return _result_iter([call(*args[0])], use_result_objects) - pool = eventlet.greenpool.GreenPool(self.size) - return _result_iter(pool.starmap(call, args), use_result_objects) - - def starcall(self, args, **kw): - use_result_objects = kw.get('use_result_objects', False) - def call(func, *args): - with local_base_config(self.base_config): - try: - return func(*args) - except Exception: - if use_result_objects: - return sys.exc_info() - else: - raise - if len(args) == 1: - eventlet.sleep() - return _result_iter([call(args[0][0], *args[0][1:])], use_result_objects) - pool = eventlet.greenpool.GreenPool(self.size) - return _result_iter(pool.starmap(call, args), use_result_objects) - - -class ThreadWorker(threading.Thread): - def __init__(self, task_queue, result_queue): - threading.Thread.__init__(self) - self.task_queue = task_queue - self.result_queue = result_queue - self.base_config = base_config() - def run(self): - with local_base_config(self.base_config): - while True: - task = self.task_queue.get() - if task is None: - self.task_queue.task_done() - break - exec_id, func, args = task - try: - result = func(*args) - except Exception: - result = sys.exc_info() - self.result_queue.put((exec_id, result)) - self.task_queue.task_done() - - -def _consume_queue(queue): - """ - Get all items from queue. - """ - while not queue.empty(): - try: - queue.get(block=False) - queue.task_done() - except Queue.Empty: - pass - - -class ThreadPool(object): - def __init__(self, size=4): - self.pool_size = size - self.task_queue = Queue.Queue() - self.result_queue = Queue.Queue() - self.pool = None - def map_each(self, func_args, raise_exceptions): - """ - args should be a list of function arg tuples. - map_each calls each function with the given arg. - """ - if self.pool_size < 2: - for func, arg in func_args: - try: - yield func(*arg) - except Exception: - yield sys.exc_info() - raise StopIteration() - - self.pool = self._init_pool() - - i = 0 - for i, (func, arg) in enumerate(func_args): - self.task_queue.put((i, func, arg)) - - results = {} - - next_result = 0 - for value in self._get_results(next_result, results, raise_exceptions): - yield value - next_result += 1 - - self.task_queue.join() - for value in self._get_results(next_result, results, raise_exceptions): - yield value - next_result += 1 - - self.shutdown() - - def _single_call(self, func, args, use_result_objects): - try: - result = func(*args) - except Exception: - if not use_result_objects: - raise - result = sys.exc_info() - return _result_iter([result], use_result_objects) - - def map(self, func, *args, **kw): - return list(self.imap(func, *args, **kw)) - - def imap(self, func, *args, **kw): - use_result_objects = kw.get('use_result_objects', False) - if len(args[0]) == 1: - return self._single_call(func, next(iter(zip(*args))), use_result_objects) - return _result_iter(self.map_each([(func, arg) for arg in zip(*args)], raise_exceptions=not use_result_objects), - use_result_objects) - - def starmap(self, func, args, **kw): - use_result_objects = kw.get('use_result_objects', False) - if len(args[0]) == 1: - return self._single_call(func, args[0], use_result_objects) - - return _result_iter(self.map_each([(func, arg) for arg in args], raise_exceptions=not use_result_objects), - use_result_objects) - - def starcall(self, args, **kw): - def call(func, *args): - return func(*args) - return self.starmap(call, args, **kw) - - def _get_results(self, next_result, results, raise_exceptions): - for i, value in self._fetch_results(raise_exceptions): - if i == next_result: - yield value - next_result += 1 - while next_result in results: - yield results.pop(next_result) - next_result += 1 - else: - results[i] = value - - def _fetch_results(self, raise_exceptions): - while not self.task_queue.empty() or not self.result_queue.empty(): - task_result = self.result_queue.get() - if (raise_exceptions and isinstance(task_result[1], tuple) and - len(task_result[1]) == 3 and - isinstance(task_result[1][1], Exception)): - self.shutdown(force=True) - exc_class, exc, tb = task_result[1] - if PY2: - exec('raise exc_class, exc, tb') - else: - raise exc.with_traceback(tb) - yield task_result - - def shutdown(self, force=False): - """ - Send shutdown sentinel to all executor threads. If `force` is True, - clean task_queue and result_queue. - """ - if force: - _consume_queue(self.task_queue) - _consume_queue(self.result_queue) - for _ in range(self.pool_size): - self.task_queue.put(None) - - def _init_pool(self): - if self.pool_size < 2: - return [] - pool = [] - for _ in range(self.pool_size): - t = ThreadWorker(self.task_queue, self.result_queue) - t.daemon = True - t.start() - pool.append(t) - return pool - - -def imap_async_eventlet(func, *args): - pool = EventletPool() - return pool.imap(func, *args) - -def imap_async_threaded(func, *args): - pool = ThreadPool(min(len(args[0]), MAX_MAP_ASYNC_THREADS)) - return pool.imap(func, *args) - -def starmap_async_eventlet(func, args): - pool = EventletPool() - return pool.starmap(func, args) - -def starmap_async_threaded(func, args): - pool = ThreadPool(min(len(args[0]), MAX_MAP_ASYNC_THREADS)) - return pool.starmap(func, args) - -def starcall_async_eventlet(args): - pool = EventletPool() - return pool.starcall(args) - -def starcall_async_threaded(args): - pool = ThreadPool(min(len(args[0]), MAX_MAP_ASYNC_THREADS)) - return pool.starcall(args) - - -def run_non_blocking_eventlet(func, args, kw={}): - return eventlet.tpool.execute(func, *args, **kw) - -def run_non_blocking_threaded(func, args, kw={}): - return func(*args, **kw) - - -def import_module(module): - """ - Import ``module``. Import patched version if eventlet is used. - """ - if uses_eventlet: - return eventlet.import_patched(module) - else: - return __import__(module) - -uses_eventlet = False - -# socket should be monkey patched when MapProxy runs inside eventlet -if _has_eventlet and eventlet.patcher.is_monkey_patched('socket'): - uses_eventlet = True - log_system.info('using eventlet for asynchronous operations') - imap = imap_async_eventlet - starmap = starmap_async_eventlet - starcall = starcall_async_eventlet - Pool = EventletPool - run_non_blocking = run_non_blocking_eventlet -else: - imap = imap_async_threaded - starmap = starmap_async_threaded - starcall = starcall_async_threaded - Pool = ThreadPool - run_non_blocking = run_non_blocking_threaded diff -Nru mapproxy-1.11.0/mapproxy/util/coverage.py mapproxy-1.12.0/mapproxy/util/coverage.py --- mapproxy-1.11.0/mapproxy/util/coverage.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/util/coverage.py 2019-08-30 07:34:08.000000000 +0000 @@ -120,6 +120,9 @@ return MapExtent(self.bbox, self.srs) def _bbox_in_coverage_srs(self, bbox, srs): + if len(bbox) == 2: + # point to bbox + bbox = [bbox[0], bbox[1], bbox[0], bbox[1]] if srs != self.srs: bbox = srs.transform_bbox_to(self.srs, bbox) return bbox @@ -323,4 +326,4 @@ if intersection.is_empty: raise EmptyGeometryError("intersection did not return any geometry") - return GeomCoverage(intersection, srs=srs, clip=clip) \ No newline at end of file + return GeomCoverage(intersection, srs=srs, clip=clip) diff -Nru mapproxy-1.11.0/mapproxy/util/ext/tempita/compat3.py mapproxy-1.12.0/mapproxy/util/ext/tempita/compat3.py --- mapproxy-1.11.0/mapproxy/util/ext/tempita/compat3.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/util/ext/tempita/compat3.py 2019-08-30 07:34:08.000000000 +0000 @@ -5,6 +5,7 @@ if sys.version < "3": b = bytes = str basestring_ = basestring + text = unicode else: def b(s): @@ -13,7 +14,7 @@ return bytes(s) basestring_ = (bytes, str) bytes = bytes -text = str + text = str if sys.version < "3": @@ -39,7 +40,7 @@ else: attr = '__str__' if hasattr(v, attr): - return unicode(v) + return text(v) else: return bytes(v) return v diff -Nru mapproxy-1.11.0/mapproxy/util/ext/tempita/_looper.py mapproxy-1.12.0/mapproxy/util/ext/tempita/_looper.py --- mapproxy-1.11.0/mapproxy/util/ext/tempita/_looper.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/util/ext/tempita/_looper.py 2019-08-30 07:34:08.000000000 +0000 @@ -6,10 +6,10 @@ These can be awkward to manage in a normal Python loop, but using the looper you can get a better sense of the context. Use like:: - >>> for loop, item in looper(['a', 'b', 'c']): - ... print loop.number, item - ... if not loop.last: - ... print '---' + >> for loop, item in looper(['a', 'b', 'c']): + .. print loop.number, item + .. if not loop.last: + .. print '---' 1 a --- 2 b diff -Nru mapproxy-1.11.0/mapproxy/util/ext/wmsparse/test/test_parse.py mapproxy-1.12.0/mapproxy/util/ext/wmsparse/test/test_parse.py --- mapproxy-1.11.0/mapproxy/util/ext/wmsparse/test/test_parse.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/util/ext/wmsparse/test/test_parse.py 2019-08-30 07:34:08.000000000 +0000 @@ -2,106 +2,161 @@ from ..parse import parse_capabilities -from nose.tools import eq_ def local_filename(filename): return os.path.join(os.path.dirname(__file__), filename) class TestWMS111(object): + def test_parse_metadata(self): - cap = parse_capabilities(local_filename('wms-omniscale-111.xml')) + cap = parse_capabilities(local_filename("wms-omniscale-111.xml")) md = cap.metadata() - eq_(md['name'], 'OGC:WMS') - eq_(md['title'], 'Omniscale OpenStreetMap WMS') - eq_(md['access_constraints'], 'Here be dragons.') - eq_(md['fees'], 'none') - eq_(md['online_resource'], 'http://omniscale.de/') - eq_(md['abstract'], 'Omniscale OpenStreetMap WMS (powered by MapProxy)') - - - eq_(md['contact']['person'], 'Oliver Tonnhofer') - eq_(md['contact']['organization'], 'Omniscale') - eq_(md['contact']['position'], 'Technical Director') - eq_(md['contact']['address'], 'Nadorster Str. 60') - eq_(md['contact']['city'], 'Oldenburg') - eq_(md['contact']['postcode'], '26123') - eq_(md['contact']['country'], 'Germany') - eq_(md['contact']['phone'], '+49(0)441-9392774-0') - eq_(md['contact']['fax'], '+49(0)441-9392774-9') - eq_(md['contact']['email'], 'osm@omniscale.de') - + assert md["name"] == "OGC:WMS" + assert md["title"] == "Omniscale OpenStreetMap WMS" + assert md["access_constraints"] == "Here be dragons." + assert md["fees"] == "none" + assert md["online_resource"] == "http://omniscale.de/" + assert md["abstract"] == "Omniscale OpenStreetMap WMS (powered by MapProxy)" + + assert md["contact"]["person"] == "Oliver Tonnhofer" + assert md["contact"]["organization"] == "Omniscale" + assert md["contact"]["position"] == "Technical Director" + assert md["contact"]["address"] == "Nadorster Str. 60" + assert md["contact"]["city"] == "Oldenburg" + assert md["contact"]["postcode"] == "26123" + assert md["contact"]["country"] == "Germany" + assert md["contact"]["phone"] == "+49(0)441-9392774-0" + assert md["contact"]["fax"] == "+49(0)441-9392774-9" + assert md["contact"]["email"] == "osm@omniscale.de" def test_parse_layer(self): - cap = parse_capabilities(local_filename('wms-omniscale-111.xml')) + cap = parse_capabilities(local_filename("wms-omniscale-111.xml")) lyrs = cap.layers_list() - eq_(len(lyrs), 2) - eq_(lyrs[0]['llbbox'], [-180.0, -85.0511287798, 180.0, 85.0511287798]) - eq_(lyrs[0]['srs'], - set(['EPSG:4326', 'EPSG:4258', 'CRS:84', 'EPSG:900913', 'EPSG:31466', - 'EPSG:31467', 'EPSG:31468', 'EPSG:25831', 'EPSG:25832', - 'EPSG:25833', 'EPSG:3857', - ]) + assert len(lyrs) == 2 + assert lyrs[0]["llbbox"] == [-180.0, -85.0511287798, 180.0, 85.0511287798] + assert lyrs[0]["srs"] == set( + [ + "EPSG:4326", + "EPSG:4258", + "CRS:84", + "EPSG:900913", + "EPSG:31466", + "EPSG:31467", + "EPSG:31468", + "EPSG:25831", + "EPSG:25832", + "EPSG:25833", + "EPSG:3857", + ] ) - eq_(len(lyrs[0]['bbox_srs']), 1) - eq_(lyrs[0]['bbox_srs']['EPSG:4326'], [-180.0, -85.0511287798, 180.0, 85.0511287798]) - + assert len(lyrs[0]["bbox_srs"]) == 1 + assert lyrs[0]["bbox_srs"]["EPSG:4326"] == [ + -180.0, + -85.0511287798, + 180.0, + 85.0511287798, + ] def test_parse_layer_2(self): - cap = parse_capabilities(local_filename('wms-large-111.xml')) + cap = parse_capabilities(local_filename("wms-large-111.xml")) lyrs = cap.layers_list() - eq_(len(lyrs), 46) - eq_(lyrs[0]['llbbox'], [-10.4, 35.7, 43.0, 74.1]) - eq_(lyrs[0]['srs'], - set(['EPSG:31467', 'EPSG:31466', 'EPSG:31465', 'EPSG:31464', - 'EPSG:31463', 'EPSG:31462', 'EPSG:4326', 'EPSG:31469', 'EPSG:31468', - 'EPSG:31257', 'EPSG:31287', 'EPSG:31286', 'EPSG:31285', 'EPSG:31284', - 'EPSG:31258', 'EPSG:31259', 'EPSG:31492', 'EPSG:31493', 'EPSG:25833', - 'EPSG:25832', 'EPSG:31494', 'EPSG:31495', 'EPSG:28992', - ]) - ) - eq_(lyrs[1]['name'], 'Grenzen') - eq_(lyrs[1]['legend']['url'], - "http://example.org/service?SERVICE=WMS&version=1.1.1&service=WMS&request=GetLegendGraphic&layer=Grenzen&format=image/png&STYLE=default" + assert len(lyrs) == 46 + assert lyrs[0]["llbbox"] == [-10.4, 35.7, 43.0, 74.1] + assert lyrs[0]["srs"] == set( + [ + "EPSG:31467", + "EPSG:31466", + "EPSG:31465", + "EPSG:31464", + "EPSG:31463", + "EPSG:31462", + "EPSG:4326", + "EPSG:31469", + "EPSG:31468", + "EPSG:31257", + "EPSG:31287", + "EPSG:31286", + "EPSG:31285", + "EPSG:31284", + "EPSG:31258", + "EPSG:31259", + "EPSG:31492", + "EPSG:31493", + "EPSG:25833", + "EPSG:25832", + "EPSG:31494", + "EPSG:31495", + "EPSG:28992", + ] ) + assert lyrs[1]["name"] == "Grenzen" + assert ( + lyrs[1]["legend"]["url"] + == "http://example.org/service?SERVICE=WMS&version=1.1.1&service=WMS&request=GetLegendGraphic&layer=Grenzen&format=image/png&STYLE=default" + ) + class TestWMS130(object): + def test_parse_metadata(self): - cap = parse_capabilities(local_filename('wms-omniscale-130.xml')) + cap = parse_capabilities(local_filename("wms-omniscale-130.xml")) md = cap.metadata() - eq_(md['name'], 'WMS') - eq_(md['title'], 'Omniscale OpenStreetMap WMS') + assert md["name"] == "WMS" + assert md["title"] == "Omniscale OpenStreetMap WMS" req = cap.requests() - eq_(req['GetMap'], 'http://osm.omniscale.net/proxy/service?') + assert req["GetMap"] == "http://osm.omniscale.net/proxy/service?" def test_parse_layer(self): - cap = parse_capabilities(local_filename('wms-omniscale-130.xml')) + cap = parse_capabilities(local_filename("wms-omniscale-130.xml")) lyrs = cap.layers_list() - eq_(len(lyrs), 2) - eq_(lyrs[0]['llbbox'], [-180.0, -85.0511287798, 180.0, 85.0511287798]) - eq_(lyrs[0]['srs'], - set(['EPSG:4326', 'EPSG:4258', 'CRS:84', 'EPSG:900913', 'EPSG:31466', - 'EPSG:31467', 'EPSG:31468', 'EPSG:25831', 'EPSG:25832', - 'EPSG:25833', 'EPSG:3857', - ]) - ) - eq_(len(lyrs[0]['bbox_srs']), 4) - eq_(set(lyrs[0]['bbox_srs'].keys()), set(['CRS:84', 'EPSG:900913', 'EPSG:4326', 'EPSG:3857'])) - eq_(lyrs[0]['bbox_srs']['EPSG:3857'], [-20037508.3428, -20037508.3428, 20037508.3428, 20037508.3428]) + assert len(lyrs) == 2 + assert lyrs[0]["llbbox"] == [-180.0, -85.0511287798, 180.0, 85.0511287798] + assert lyrs[0]["srs"] == set( + [ + "EPSG:4326", + "EPSG:4258", + "CRS:84", + "EPSG:900913", + "EPSG:31466", + "EPSG:31467", + "EPSG:31468", + "EPSG:25831", + "EPSG:25832", + "EPSG:25833", + "EPSG:3857", + ] + ) + assert len(lyrs[0]["bbox_srs"]) == 4 + assert set(lyrs[0]["bbox_srs"].keys()) == set( + ["CRS:84", "EPSG:900913", "EPSG:4326", "EPSG:3857"] + ) + assert lyrs[0]["bbox_srs"]["EPSG:3857"] == [ + -20037508.3428, + -20037508.3428, + 20037508.3428, + 20037508.3428, + ] # EPSG:4326 bbox should be switched to long/lat - eq_(lyrs[0]['bbox_srs']['EPSG:4326'], (-180.0, -85.0511287798, 180.0, 85.0511287798)) + assert lyrs[0]["bbox_srs"]["EPSG:4326"] == ( + -180.0, + -85.0511287798, + 180.0, + 85.0511287798, + ) class TestLargeWMSCapabilities(object): + def test_parse_metadata(self): - cap = parse_capabilities(local_filename('wms_nasa_cap.xml')) + cap = parse_capabilities(local_filename("wms_nasa_cap.xml")) md = cap.metadata() - eq_(md['name'], 'OGC:WMS') - eq_(md['title'], 'JPL Global Imagery Service') + assert md["name"] == "OGC:WMS" + assert md["title"] == "JPL Global Imagery Service" def test_parse_layer(self): - cap = parse_capabilities(local_filename('wms_nasa_cap.xml')) + cap = parse_capabilities(local_filename("wms_nasa_cap.xml")) lyrs = cap.layers_list() - eq_(len(lyrs), 15) - eq_(len(lyrs[0]['bbox_srs']), 0) + assert len(lyrs) == 15 + assert len(lyrs[0]["bbox_srs"]) == 0 diff -Nru mapproxy-1.11.0/mapproxy/util/ext/wmsparse/test/test_util.py mapproxy-1.12.0/mapproxy/util/ext/wmsparse/test/test_util.py --- mapproxy-1.11.0/mapproxy/util/ext/wmsparse/test/test_util.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/util/ext/wmsparse/test/test_util.py 2019-08-30 07:34:08.000000000 +0000 @@ -1,16 +1,23 @@ from ..util import resolve_ns -from nose.tools import eq_ def test_resolve_ns(): - eq_(resolve_ns('/bar/bar', {}, None), - '/bar/bar') + assert resolve_ns("/bar/bar", {}, None) == "/bar/bar" - eq_(resolve_ns('/bar/bar', {}, 'http://foo'), - '/{http://foo}bar/{http://foo}bar') + assert ( + resolve_ns("/bar/bar", {}, "http://foo") == "/{http://foo}bar/{http://foo}bar" + ) - eq_(resolve_ns('/bar/xlink:bar', {'xlink': 'http://www.w3.org/1999/xlink'}, 'http://foo'), - '/{http://foo}bar/{http://www.w3.org/1999/xlink}bar') + assert ( + resolve_ns( + "/bar/xlink:bar", {"xlink": "http://www.w3.org/1999/xlink"}, "http://foo" + ) + == "/{http://foo}bar/{http://www.w3.org/1999/xlink}bar" + ) - eq_(resolve_ns('bar/xlink:bar', {'xlink': 'http://www.w3.org/1999/xlink'}, 'http://foo'), - '{http://foo}bar/{http://www.w3.org/1999/xlink}bar') + assert ( + resolve_ns( + "bar/xlink:bar", {"xlink": "http://www.w3.org/1999/xlink"}, "http://foo" + ) + == "{http://foo}bar/{http://www.w3.org/1999/xlink}bar" + ) diff -Nru mapproxy-1.11.0/mapproxy/util/fs.py mapproxy-1.12.0/mapproxy/util/fs.py --- mapproxy-1.11.0/mapproxy/util/fs.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/util/fs.py 2019-08-30 07:34:08.000000000 +0000 @@ -124,7 +124,7 @@ # where file locking does not work (network fs) path_tmp = filename + '.tmp-' + str(random.randint(0, 99999999)) try: - fd = os.open(path_tmp, os.O_EXCL | os.O_CREAT | os.O_WRONLY, 0o666) + fd = os.open(path_tmp, os.O_EXCL | os.O_CREAT | os.O_WRONLY, 0o664) with os.fdopen(fd, 'wb') as f: f.write(data) os.rename(path_tmp, filename) @@ -135,7 +135,8 @@ pass raise ex else: - with open(filename, 'wb') as f: + fd = os.open(filename, os.O_CREAT | os.O_WRONLY, 0o664) + with os.fdopen(fd, 'wb') as f: f.write(data) diff -Nru mapproxy-1.11.0/mapproxy/util/wsgi.py mapproxy-1.12.0/mapproxy/util/wsgi.py --- mapproxy-1.11.0/mapproxy/util/wsgi.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/util/wsgi.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,41 +0,0 @@ -# This file is part of the MapProxy project. -# Copyright (C) 2010 Omniscale <http://omniscale.de> -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -""" -WSGI utils -""" - -def lighttpd_root_fix_filter_factory(global_conf): - return LighttpdCGIRootFix - -class LighttpdCGIRootFix(object): - """Wrap the application in this middleware if you are using lighttpd - with FastCGI or CGI and the application is mounted on the URL root. - - :param app: the WSGI application - """ - - def __init__(self, app): - self.app = app - - def __call__(self, environ, start_response): - script_name = environ.get('SCRIPT_NAME', '') - path_info = environ.get('PATH_INFO', '') - if path_info == script_name: - environ['PATH_INFO'] = path_info - else: - environ['PATH_INFO'] = script_name + path_info - environ['SCRIPT_NAME'] = '' - return self.app(environ, start_response) diff -Nru mapproxy-1.11.0/mapproxy/util/yaml.py mapproxy-1.12.0/mapproxy/util/yaml.py --- mapproxy-1.11.0/mapproxy/util/yaml.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/util/yaml.py 2019-08-30 07:34:08.000000000 +0000 @@ -30,19 +30,27 @@ return load_yaml(f) return load_yaml(file_or_filename) -def load_yaml(doc): - """ - Load yaml from file object or string. - """ +def _load_yaml(doc): + # try different methods to load yaml try: if getattr(yaml, '__with_libyaml__', False): try: - return yaml.load(doc, Loader=yaml.CLoader) + return yaml.load(doc, Loader=yaml.CSafeLoader) except AttributeError: # handle cases where __with_libyaml__ is True but # CLoader doesn't work (missing .dispose()) - return yaml.load(doc) - return yaml.load(doc) + return yaml.safe_load(doc) + return yaml.safe_load(doc) except (yaml.scanner.ScannerError, yaml.parser.ParserError) as ex: raise YAMLError(str(ex)) +def load_yaml(doc): + """ + Load yaml from file object or string. + """ + data = _load_yaml(doc) + if type(data) is not dict: + # all configs are dicts, raise YAMLError to prevent later AttributeErrors (#352) + raise YAMLError("configuration not a YAML dictionary") + return data + diff -Nru mapproxy-1.11.0/mapproxy/wsgiapp.py mapproxy-1.12.0/mapproxy/wsgiapp.py --- mapproxy-1.11.0/mapproxy/wsgiapp.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/mapproxy/wsgiapp.py 2019-08-30 07:34:08.000000000 +0000 @@ -17,12 +17,11 @@ The WSGI application. """ from __future__ import print_function -import re +import logging import os -import sys -import time +import re import threading -import warnings +import time try: # time.strptime is thread-safe, but not the first call. @@ -37,60 +36,9 @@ from mapproxy.config import local_base_config from mapproxy.config.loader import load_configuration, ConfigurationError -import logging log = logging.getLogger('mapproxy.config') log_wsgiapp = logging.getLogger('mapproxy.wsgiapp') -def app_factory(global_options, mapproxy_conf, **local_options): - """ - Paster app_factory. - """ - conf = global_options.copy() - conf.update(local_options) - log_conf = conf.get('log_conf', None) - reload_files = conf.get('reload_files', None) - if reload_files is not None: - init_paster_reload_files(reload_files) - - init_logging_system(log_conf, os.path.dirname(mapproxy_conf)) - - return make_wsgi_app(mapproxy_conf) - -def init_paster_reload_files(reload_files): - file_patterns = reload_files.split('\n') - file_patterns.append(os.path.join(os.path.dirname(__file__), 'defaults.yaml')) - init_paster_file_watcher(file_patterns) - -def init_paster_file_watcher(file_patterns): - from glob import glob - for pattern in file_patterns: - files = glob(pattern) - _add_files_to_paster_file_watcher(files) - -def _add_files_to_paster_file_watcher(files): - import paste.reloader - for file in files: - paste.reloader.watch_file(file) - -def init_logging_system(log_conf, base_dir): - import logging.config - try: - import cloghandler # adds CRFHandler to log handlers - cloghandler.ConcurrentRotatingFileHandler #disable pyflakes warning - except ImportError: - pass - if log_conf: - if not os.path.exists(log_conf): - print('ERROR: log configuration %s not found.' % log_conf, file=sys.stderr) - return - logging.config.fileConfig(log_conf, dict(here=base_dir)) - -def init_null_logging(): - import logging - class NullHandler(logging.Handler): - def emit(self, record): - pass - logging.getLogger().addHandler(NullHandler()) def make_wsgi_app(services_conf=None, debug=False, ignore_config_warnings=True, reloader=False): """ diff -Nru mapproxy-1.11.0/release.py mapproxy-1.12.0/release.py --- mapproxy-1.11.0/release.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/release.py 2019-08-30 07:34:08.000000000 +0000 @@ -1,11 +1,3 @@ -try: - from nose.plugins.skip import SkipTest - import sys - if 'nosetest' in ''.join(sys.argv): - raise SkipTest() -except ImportError: - pass - import scriptine from scriptine import path from scriptine.shell import backtick_, sh diff -Nru mapproxy-1.11.0/requirements-appveyor.txt mapproxy-1.12.0/requirements-appveyor.txt --- mapproxy-1.11.0/requirements-appveyor.txt 1970-01-01 00:00:00.000000000 +0000 +++ mapproxy-1.12.0/requirements-appveyor.txt 2019-08-30 07:34:08.000000000 +0000 @@ -0,0 +1,4 @@ +WebTest==2.0.25 +pytest==3.6.0 +WebOb==1.7.1 +requests==2.20.0 diff -Nru mapproxy-1.11.0/requirements-tests.txt mapproxy-1.12.0/requirements-tests.txt --- mapproxy-1.11.0/requirements-tests.txt 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/requirements-tests.txt 2019-08-30 07:34:08.000000000 +0000 @@ -1,33 +1,66 @@ -WebTest==2.0.25 -lxml==3.7.3 -nose==1.3.7 -Shapely==1.5.17 -PyYAML==3.12 -Pillow==4.0.0 -WebOb==1.7.1 -coverage==4.3.4 -requests==2.13.0 -boto3==1.4.4 -moto==0.4.31 -eventlet==0.20.1 -beautifulsoup4==4.5.3 -boto==2.46.1 -botocore==1.5.14 -docutils==0.13.1 +Jinja2==2.10.1 +MarkupSafe==1.0 +Pillow==5.1.0 +PyYAML==5.1 +Shapely==1.6.4.post1 +WebOb==1.8.1 +WebTest==2.0.29 +Werkzeug==0.14.1 +asn1crypto==0.24.0 +atomicwrites==1.1.5 +attrs==18.1.0 +aws-xray-sdk==0.95 +backports.ssl-match-hostname==3.5.0.1 +backports.tempfile==1.0 +backports.weakref==1.0.post1 +basho-erlastic==2.1.1 +beautifulsoup4==4.6.0 +boto==2.48.0 +boto3==1.7.27 +botocore==1.10.27 +certifi==2018.4.16 +cffi==1.11.5 +chardet==3.0.4 +cookies==2.2.1 +coverage==4.5.1 +cryptography==2.3.0 +docker==3.3.0 +docker-pycreds==0.2.3 +docutils==0.14 enum-compat==0.0.2 -futures==3.0.5 -greenlet==0.4.12 -riak==2.6.1 -httpretty==0.8.10 -Jinja2==2.9.5 -jmespath==0.9.1 -MarkupSafe==0.23 -olefile==0.44 -python-dateutil==2.6.0 -pytz==2016.10 -s3transfer==0.1.10 -six==1.10.0 -waitress==1.0.2 -Werkzeug==0.11.15 -xmltodict==0.10.2 -redis==2.10.5 +enum34==1.1.6 +funcsigs==1.0.2 +futures==3.2.0;python_version<"3.0" +greenlet==0.4.13 +httpretty==0.9.4 +idna==2.6 +ipaddress==1.0.22 +jmespath==0.9.3 +jsondiff==1.1.1 +jsonpickle==0.9.6 +lxml==4.2.1 +mock==2.0.0 +more-itertools==4.2.0 +moto==1.3.3 +olefile==0.45.1 +pbr==4.0.3 +pluggy==0.6.0 +protobuf==2.6.1 +py==1.5.3 +pyOpenSSL==18.0.0 +pyaml==17.12.1 +pycparser==2.18 +pytest==3.6.0 +python-dateutil==2.6.1 +pytz==2018.4 +redis==2.10.6 +requests==2.20.0 +responses==0.9.0 +riak==2.7.0 +s3transfer==0.1.13 +six==1.11.0 +urllib3==1.24.2 +waitress==1.1.0 +websocket-client==0.47.0 +wrapt==1.10.11 +xmltodict==0.11.0 diff -Nru mapproxy-1.11.0/requirements-travis.txt mapproxy-1.12.0/requirements-travis.txt --- mapproxy-1.11.0/requirements-travis.txt 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/requirements-travis.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -WebTest==1.4.0 -webob==1.1.1 -lxml==2.3.5 -mocker==1.1.1 -nose==1.1.2 -Shapely==1.2.15 -PyYAML==3.10 -Pillow==1.7.7 -eventlet==0.9.17 -riak==2.6.1 diff -Nru mapproxy-1.11.0/setup.cfg mapproxy-1.12.0/setup.cfg --- mapproxy-1.11.0/setup.cfg 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/setup.cfg 2019-08-30 07:34:08.000000000 +0000 @@ -1,12 +1,12 @@ -[nosetests] -cover-erase = 1 -verbosity = 2 -doctest-tests = 1 -with-doctest = 1 +[tool:pytest] +addopts = --doctest-modules --verbose +doctest_optionflags= NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL ALLOW_UNICODE ALLOW_BYTES +norecursedirs = fixture .git .tox +testpaths = mapproxy [egg_info] #tag_build = .dev tag_date = true [bdist_wheel] -universal=1 \ No newline at end of file +universal=1 diff -Nru mapproxy-1.11.0/setup.py mapproxy-1.12.0/setup.py --- mapproxy-1.11.0/setup.py 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/setup.py 2019-08-30 07:34:08.000000000 +0000 @@ -4,7 +4,7 @@ install_requires = [ - 'PyYAML>=3.0,<3.99', + 'PyYAML>=3.0', ] def package_installed(pkg): @@ -54,7 +54,7 @@ setup( name='MapProxy', - version="1.11.0", + version="1.12.0", description='An accelerating proxy for tile and web map services', long_description=long_description(7), author='Oliver Tonnhofer', @@ -68,16 +68,6 @@ 'mapproxy-seed = mapproxy.seed.script:main', 'mapproxy-util = mapproxy.script.util:main', ], - 'paste.app_factory': [ - 'app = mapproxy.wsgiapp:app_factory', - 'multiapp = mapproxy.multiapp:app_factory' - ], - 'paste.paster_create_template': [ - 'mapproxy_conf=mapproxy.config_template:PasterConfigurationTemplate' - ], - 'paste.filter_factory': [ - 'lighttpd_root_fix = mapproxy.util.wsgi:lighttpd_root_fix_filter_factory', - ], }, package_data = {'': ['*.xml', '*.yaml', '*.ttf', '*.wsgi', '*.ini']}, install_requires=install_requires, @@ -95,5 +85,4 @@ "Topic :: Scientific/Engineering :: GIS", ], zip_safe=False, - test_suite='nose.collector', ) diff -Nru mapproxy-1.11.0/tox.ini mapproxy-1.12.0/tox.ini --- mapproxy-1.11.0/tox.ini 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/tox.ini 2019-08-30 07:34:08.000000000 +0000 @@ -1,21 +1,10 @@ [tox] -envlist = py26,py27,py33,py34,docs +envlist = py27,py34,py36,docs [testenv] -commands = nosetests --with-xunit --xunit-file=nosetests-{envname}.xml mapproxy -deps = - WebTest==2.0.10 - lxml==3.2.4 - nose==1.3.0 - Shapely==1.3.2 - PyYAML==3.10 - Pillow==2.3.1 - WebOb==1.2.3 - beautifulsoup4==4.3.2 - coverage==3.7 - requests==2.0.1 - six==1.4.1 - waitress==0.8.7 +commands = + pytest mapproxy +deps = -rrequirements-tests.txt [testenv:hash] setenv = diff -Nru mapproxy-1.11.0/.travis.yml mapproxy-1.12.0/.travis.yml --- mapproxy-1.11.0/.travis.yml 2017-11-20 12:25:56.000000000 +0000 +++ mapproxy-1.12.0/.travis.yml 2019-08-30 07:34:08.000000000 +0000 @@ -2,23 +2,24 @@ python: - "2.7" - - "3.3" - "3.4" - "3.5" - "3.6" + - "3.7" services: - couchdb - - riak - redis-server addons: apt: + sources: + - sourceline: 'deb https://packagecloud.io/basho/riak/ubuntu/ xenial main' + key_url: 'https://packagecloud.io/basho/riak/gpgkey' packages: - - libproj0 + - proj-bin - libgeos-dev - libgdal-dev - - libgdal1h - libxslt1-dev - libxml2-dev - build-essential @@ -28,11 +29,13 @@ - libfreetype6-dev - protobuf-compiler - libprotoc-dev + - riak env: global: - MAPPROXY_TEST_COUCHDB=http://127.0.0.1:5984 - MAPPROXY_TEST_REDIS=127.0.0.1:6379 + - MAPPROXY_TEST_RIAK_HTTP=http://localhost:8098 - MAPPROXY_TEST_RIAK_PBC=pbc://localhost:8087 @@ -41,11 +44,11 @@ - BOTO_CONFIG=/doesnotexist matrix: - # Test 2.7 and 3.6 also with latest Pillow version include: + # Test 2.7 and 3.7 also with latest Pillow version - python: "2.7" env: USE_LATEST_PILLOW=1 - - python: "3.6" + - python: "3.7" env: USE_LATEST_PILLOW=1 cache: @@ -53,11 +56,12 @@ - $HOME/.cache/pip before_install: - - echo -n "ulimit -n 4096" | sudo tee /etc/default/riak && sudo service riak restart # default open file limit is too low for riak + - "sudo systemctl start riak" install: - "pip install -r requirements-tests.txt" - "if [[ $USE_LATEST_PILLOW = '1' ]]; then pip install -U Pillow; fi" - "pip freeze" -script: nosetests mapproxy +script: + - pytest mapproxy