Skip to content

Commit fd77ee2

Browse files
committedOct 4, 2016
Backport of the new test pick a free port strategy
1 parent a6bb8a9 commit fd77ee2

File tree

6 files changed

+94
-72
lines changed

6 files changed

+94
-72
lines changed
 

‎tests/src/python/offlineditingtestbase.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def _setUp(self):
6262
def _tearDown(self):
6363
"""Called by tearDown: run after each test."""
6464
# Clear test layers
65-
self._clearLayer('test_point')
65+
self._clearLayer(self._getLayer('test_point'))
6666

6767
@classmethod
6868
def _compareFeature(cls, layer, attributes):
@@ -71,11 +71,10 @@ def _compareFeature(cls, layer, attributes):
7171
return f['name'] == attributes[1] and f.geometry().asPoint().toString() == attributes[2].toString()
7272

7373
@classmethod
74-
def _clearLayer(cls, layer_name):
74+
def _clearLayer(cls, layer):
7575
"""
7676
Delete all features from the backend layer
7777
"""
78-
layer = cls._getLayer(layer_name)
7978
layer.startEditing()
8079
layer.deleteFeatures([f.id() for f in layer.getFeatures()])
8180
layer.commitChanges()

‎tests/src/python/qgis_wrapped_server.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
QGIS Server HTTP wrapper
44
55
This script launches a QGIS Server listening on port 8081 or on the port
6-
specified on the environment variable QGIS_SERVER_DEFAULT_PORT
6+
specified on the environment variable QGIS_SERVER_PORT.
7+
QGIS_SERVER_HOST (defaults to 127.0.0.1)
78
89
For testing purposes, HTTP Basic can be enabled by setting the following
910
environment variables:
@@ -29,19 +30,17 @@
2930

3031

3132
import os
33+
import sys
3234
import urllib.parse
3335
from http.server import BaseHTTPRequestHandler, HTTPServer
3436
from qgis.server import QgsServer, QgsServerFilter
3537

36-
try:
37-
QGIS_SERVER_DEFAULT_PORT = int(os.environ['QGIS_SERVER_DEFAULT_PORT'])
38-
except KeyError:
39-
QGIS_SERVER_DEFAULT_PORT = 8081
38+
QGIS_SERVER_PORT = int(os.environ.get('QGIS_SERVER_PORT', '8081'))
39+
QGIS_SERVER_HOST = os.environ.get('QGIS_SERVER_HOST', '127.0.0.1')
4040

4141
qgs_server = QgsServer()
4242

4343
if os.environ.get('QGIS_SERVER_HTTP_BASIC_AUTH') is not None:
44-
print('HTTP Basic Authorization Required username:%s password:%s' % (os.environ.get('QGIS_SERVER_USERNAME', 'username'), os.environ.get('QGIS_SERVER_PASSWORD', 'password')))
4544
import base64
4645

4746
class HTTPBasicFilter(QgsServerFilter):
@@ -96,7 +95,8 @@ def do_POST(self):
9695

9796

9897
if __name__ == '__main__':
99-
server = HTTPServer(('localhost', QGIS_SERVER_DEFAULT_PORT), Handler)
100-
print('Starting server on localhost:%s, use <Ctrl-C> to stop' %
101-
QGIS_SERVER_DEFAULT_PORT)
98+
server = HTTPServer((QGIS_SERVER_HOST, QGIS_SERVER_PORT), Handler)
99+
print('Starting server on %s:%s, use <Ctrl-C> to stop' %
100+
(QGIS_SERVER_HOST, server.server_port))
101+
sys.stdout.flush()
102102
server.serve_forever()

‎tests/src/python/test_authmanager_endpoint.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"""
1818
import os
1919
import sys
20+
import re
2021
import subprocess
2122
import tempfile
2223
import random
@@ -29,10 +30,9 @@
2930
# This will get replaced with a git SHA1 when you do a git archive
3031
__revision__ = '$Format:%H$'
3132

32-
from time import sleep
3333
from shutil import rmtree
3434

35-
from utilities import unitTestDataPath
35+
from utilities import unitTestDataPath, waitServer
3636
from qgis.core import (
3737
QgsAuthManager,
3838
QgsAuthMethodConfig,
@@ -45,13 +45,10 @@
4545
)
4646

4747
try:
48-
QGIS_SERVER_AUTHMANAGER_DEFAULT_PORT = os.environ['QGIS_SERVER_AUTHMANAGER_DEFAULT_PORT']
48+
QGIS_SERVER_ENDPOINT_PORT = os.environ['QGIS_SERVER_ENDPOINT_PORT']
4949
except:
50-
import socket
51-
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
52-
s.bind(("", 0))
53-
QGIS_SERVER_AUTHMANAGER_DEFAULT_PORT = s.getsockname()[1]
54-
s.close()
50+
QGIS_SERVER_ENDPOINT_PORT = '0' # Auto
51+
5552

5653
QGIS_AUTH_DB_DIR_PATH = tempfile.mkdtemp()
5754

@@ -66,7 +63,7 @@ class TestAuthManager(unittest.TestCase):
6663
def setUpClass(cls):
6764
"""Run before all tests:
6865
Creates an auth configuration"""
69-
cls.port = QGIS_SERVER_AUTHMANAGER_DEFAULT_PORT
66+
cls.port = QGIS_SERVER_ENDPOINT_PORT
7067
# Clean env just to be sure
7168
env_vars = ['QUERY_STRING', 'QGIS_PROJECT_FILE']
7269
for ev in env_vars:
@@ -91,12 +88,17 @@ def setUpClass(cls):
9188
os.environ['QGIS_SERVER_HTTP_BASIC_AUTH'] = '1'
9289
os.environ['QGIS_SERVER_USERNAME'] = cls.username
9390
os.environ['QGIS_SERVER_PASSWORD'] = cls.password
94-
os.environ['QGIS_SERVER_DEFAULT_PORT'] = str(cls.port)
91+
os.environ['QGIS_SERVER_PORT'] = str(cls.port)
9592
server_path = os.path.dirname(os.path.realpath(__file__)) + \
9693
'/qgis_wrapped_server.py'
9794
cls.server = subprocess.Popen([sys.executable, server_path],
98-
env=os.environ)
99-
sleep(2)
95+
env=os.environ, stdout=subprocess.PIPE)
96+
97+
line = cls.server.stdout.readline()
98+
cls.port = int(re.findall(b':(\d+)', line)[0])
99+
assert cls.port != 0
100+
# Wait for the server process to start
101+
assert waitServer('http://127.0.0.1:%s' % cls.port), "Server is not responding! http://127.0.0.1:%s" % cls.port
100102

101103
@classmethod
102104
def tearDownClass(cls):

‎tests/src/python/test_offline_editing_wfs.py

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
# -*- coding: utf-8 -*-
22
"""
33
Offline editing Tests.
4-
54
WFS-T tests need using QGIS Server through
65
qgis_wrapped_server.py.
7-
86
This is an integration test for QGIS Desktop WFS-T provider and QGIS Server
97
WFS-T that check if QGIS offline editing works with a WFS-T endpoint.
10-
118
The test uses testdata/wfs_transactional/wfs_transactional.qgs and three
129
initially empty shapefiles layers with points, lines and polygons.
13-
1410
The point layer is used in the test
15-
1611
From build dir, run: ctest -R PyQgsOfflineEditingWFS -V
17-
1812
.. note:: This program is free software; you can redistribute it and/or modify
1913
it under the terms of the GNU General Public License as published by
2014
the Free Software Foundation; either version 2 of the License, or
@@ -31,15 +25,13 @@
3125

3226
import os
3327
import sys
28+
import re
3429
import subprocess
3530
from shutil import copytree, rmtree
3631
import tempfile
3732
from time import sleep
38-
from utilities import unitTestDataPath
39-
from qgis.core import (
40-
QgsVectorLayer,
41-
QgsProject,
42-
)
33+
from utilities import unitTestDataPath, waitServer
34+
from qgis.core import QgsVectorLayer
4335

4436
from qgis.testing import (
4537
start_app,
@@ -48,27 +40,24 @@
4840

4941
from offlineditingtestbase import OfflineTestBase
5042

51-
from qgis.PyQt.QtCore import QFileInfo
5243

5344
try:
54-
QGIS_SERVER_OFFLINE_EDITING_DEFAULT_PORT = os.environ['QGIS_SERVER_OFFLINE_EDITING_DEFAULT_PORT']
45+
QGIS_SERVER_OFFLINE_PORT = os.environ['QGIS_SERVER_OFFLINE_PORT']
5546
except:
56-
import socket
57-
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
58-
s.bind(("", 0))
59-
QGIS_SERVER_OFFLINE_EDITING_DEFAULT_PORT = s.getsockname()[1]
60-
s.close()
61-
47+
QGIS_SERVER_OFFLINE_PORT = '0' # Auto
6248

6349
qgis_app = start_app()
6450

6551

6652
class TestWFST(unittest.TestCase, OfflineTestBase):
6753

54+
# To fake the WFS cache!
55+
counter = 0
56+
6857
@classmethod
6958
def setUpClass(cls):
7059
"""Run before all tests"""
71-
cls.port = QGIS_SERVER_OFFLINE_EDITING_DEFAULT_PORT
60+
cls.port = QGIS_SERVER_OFFLINE_PORT
7261
# Create tmp folder
7362
cls.temp_path = tempfile.mkdtemp()
7463
cls.testdata_path = cls.temp_path + '/' + 'wfs_transactional' + '/'
@@ -86,48 +75,57 @@ def setUpClass(cls):
8675
except KeyError:
8776
pass
8877
# Clear all test layers
89-
cls._clearLayer('test_point')
90-
os.environ['QGIS_SERVER_DEFAULT_PORT'] = str(cls.port)
91-
server_path = os.path.dirname(os.path.realpath(__file__)) + \
78+
cls._clearLayer(cls._getLayer('test_point'))
79+
os.environ['QGIS_SERVER_PORT'] = str(cls.port)
80+
cls.server_path = os.path.dirname(os.path.realpath(__file__)) + \
9281
'/qgis_wrapped_server.py'
93-
cls.server = subprocess.Popen([sys.executable, server_path],
94-
env=os.environ)
95-
sleep(2)
9682

9783
@classmethod
9884
def tearDownClass(cls):
9985
"""Run after all tests"""
100-
cls.server.terminate()
101-
del cls.server
102-
# Clear test layer
103-
cls._clearLayer('test_point')
10486
rmtree(cls.temp_path)
10587

10688
def setUp(self):
10789
"""Run before each test."""
90+
self.server = subprocess.Popen([sys.executable, self.server_path],
91+
env=os.environ, stdout=subprocess.PIPE)
92+
line = self.server.stdout.readline()
93+
self.port = int(re.findall(b':(\d+)', line)[0])
94+
assert self.port != 0
95+
# Wait for the server process to start
96+
assert waitServer('http://127.0.0.1:%s' % self.port), "Server is not responding!"
10897
self._setUp()
10998

11099
def tearDown(self):
111100
"""Run after each test."""
101+
# Clear test layer
102+
self._clearLayer(self._getOnlineLayer('test_point'))
103+
# Kill the server
104+
self.server.terminate()
105+
self.server.wait()
106+
del self.server
107+
# Delete the sqlite db
108+
os.unlink(os.path.join(self.temp_path, 'offlineDbFile.sqlite'))
112109
self._tearDown()
113110

114-
@classmethod
115-
def _getOnlineLayer(cls, type_name, layer_name=None):
111+
def _getOnlineLayer(self, type_name, layer_name=None):
116112
"""
117-
Layer factory (return the online layer), provider specific
113+
Return a new WFS layer, overriding the WFS cache
118114
"""
119115
if layer_name is None:
120116
layer_name = 'wfs_' + type_name
121117
parms = {
122118
'srsname': 'EPSG:4326',
123119
'typename': type_name,
124-
'url': 'http://127.0.0.1:%s/?map=%s' % (cls.port,
125-
cls.project_path),
120+
'url': 'http://127.0.0.1:%s/%s/?map=%s' % (self.port,
121+
self.counter,
122+
self.project_path),
126123
'version': 'auto',
127124
'table': '',
128125
#'sql': '',
129126
}
130-
uri = ' '.join([("%s='%s'" % (k, v)) for k, v in parms.items()])
127+
self.counter += 1
128+
uri = ' '.join([("%s='%s'" % (k, v)) for k, v in list(parms.items())])
131129
wfs_layer = QgsVectorLayer(uri, layer_name, 'WFS')
132130
assert wfs_layer.isValid()
133131
return wfs_layer

‎tests/src/python/test_qgsserver_wfst.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
# -*- coding: utf-8 -*-
23
"""
34
Tests for WFS-T provider using QGIS Server through qgis_wrapped_server.py.
@@ -36,11 +37,12 @@
3637

3738
import os
3839
import sys
40+
import re
3941
import subprocess
4042
from shutil import copytree, rmtree
4143
import tempfile
4244
from time import sleep
43-
from utilities import unitTestDataPath
45+
from utilities import unitTestDataPath, waitServer
4446
from qgis.core import (
4547
QgsVectorLayer,
4648
QgsFeature,
@@ -56,13 +58,9 @@
5658
)
5759

5860
try:
59-
QGIS_SERVER_WFST_DEFAULT_PORT = os.environ['QGIS_SERVER_WFST_DEFAULT_PORT']
61+
QGIS_SERVER_WFST_PORT = os.environ['QGIS_SERVER_WFST_PORT']
6062
except:
61-
import socket
62-
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
63-
s.bind(("", 0))
64-
QGIS_SERVER_WFST_DEFAULT_PORT = s.getsockname()[1]
65-
s.close()
63+
QGIS_SERVER_WFST_PORT = '0' # Auto
6664

6765

6866
qgis_app = start_app()
@@ -73,7 +71,7 @@ class TestWFST(unittest.TestCase):
7371
@classmethod
7472
def setUpClass(cls):
7573
"""Run before all tests"""
76-
cls.port = QGIS_SERVER_WFST_DEFAULT_PORT
74+
cls.port = QGIS_SERVER_WFST_PORT
7775
# Create tmp folder
7876
cls.temp_path = tempfile.mkdtemp()
7977
cls.testdata_path = cls.temp_path + '/' + 'wfs_transactional' + '/'
@@ -93,17 +91,22 @@ def setUpClass(cls):
9391
# Clear all test layers
9492
for ln in ['test_point', 'test_polygon', 'test_linestring']:
9593
cls._clearLayer(ln)
96-
os.environ['QGIS_SERVER_DEFAULT_PORT'] = str(cls.port)
94+
os.environ['QGIS_SERVER_PORT'] = str(cls.port)
9795
server_path = os.path.dirname(os.path.realpath(__file__)) + \
9896
'/qgis_wrapped_server.py'
9997
cls.server = subprocess.Popen([sys.executable, server_path],
100-
env=os.environ)
101-
sleep(2)
98+
env=os.environ, stdout=subprocess.PIPE)
99+
line = cls.server.stdout.readline()
100+
cls.port = int(re.findall(b':(\d+)', line)[0])
101+
assert cls.port != 0
102+
# Wait for the server process to start
103+
assert waitServer('http://127.0.0.1:%s' % cls.port), "Server is not responding! http://127.0.0.1:%s" % cls.port
102104

103105
@classmethod
104106
def tearDownClass(cls):
105107
"""Run after all tests"""
106108
cls.server.terminate()
109+
cls.server.wait()
107110
del cls.server
108111
# Clear all test layers
109112
for ln in ['test_point', 'test_polygon', 'test_linestring']:
@@ -156,7 +159,7 @@ def _getWFSLayer(cls, type_name, layer_name=None):
156159
'table': '',
157160
#'sql': '',
158161
}
159-
uri = ' '.join([("%s='%s'" % (k, v)) for k, v in parms.items()])
162+
uri = ' '.join([("%s='%s'" % (k, v)) for k, v in list(parms.items())])
160163
wfs_layer = QgsVectorLayer(uri, layer_name, 'WFS')
161164
assert wfs_layer.isValid()
162165
return wfs_layer

‎tests/src/python/utilities.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import glob
1919
import platform
2020
import tempfile
21+
from urllib2 import urlopen, HTTPError
2122

2223
from qgis.PyQt.QtCore import QDir
2324

@@ -815,3 +816,22 @@ def memberIsDocumented(self, member_elem):
815816
if doc is not None and list(doc):
816817
return True
817818
return False
819+
820+
821+
def waitServer(url, timeout=10):
822+
""" Wait for a server to be online and to respond
823+
HTTP errors are ignored
824+
@param timeout: in seconds
825+
@return: True of False
826+
"""
827+
from time import time as now
828+
end = now() + timeout
829+
while True:
830+
try:
831+
urlopen(url, timeout=1)
832+
return True
833+
except HTTPError:
834+
return True
835+
except Exception as e:
836+
if now() > end:
837+
return False

0 commit comments

Comments
 (0)
Please sign in to comment.