From 819669f8a63adc36aada0a3f2fcb57c0db25032a Mon Sep 17 00:00:00 2001 From: John Rouillard Date: Tue, 31 Dec 2024 23:48:38 -0500 Subject: [PATCH] test: issue2551366. Probe for open port in test_liveserver.py Add a method to probe for an open port to wsgi_liveserver.py. Start the roundup server under wsgi on the open port. If a port can't be found, it skips all tests. Also changed all hardcoded URL references to use the dynamicly determined tracker url/port value. I fed my patch to wsgi_liveserver.py upstream at: https://github.com/jerrykan/wsgi-liveserver/issues/3 --- test/test_liveserver.py | 96 +++++++++++++++++++++++------------------ test/wsgi_liveserver.py | 19 +++++++- 2 files changed, 72 insertions(+), 43 deletions(-) diff --git a/test/test_liveserver.py b/test/test_liveserver.py index b9748935..4d5fabe5 100644 --- a/test/test_liveserver.py +++ b/test/test_liveserver.py @@ -79,8 +79,19 @@ class WsgiSetup(LiveServerTestCase): # have chicken and egg issue here. Need to encode the base_url # in the config file but we don't know it until after # the server is started and has read the config.ini. - # so only allow one port number - port_range = (9001, 9001) # default is (8080, 8090) + # Probe for an unused port and set the port range to + # include only that port. + tracker_port = LiveServerTestCase.probe_ports(8080, 8100) + if tracker_port is None: + pytest.skip("Unable to find available port for server: 8080-8100", + allow_module_level=True) + port_range = (tracker_port, tracker_port) + + # set a couple of properties to use for URL generation in + # expected output or use to set TRACKER_WEB in config.ini. + tracker_web = "http://localhost:%d/" % tracker_port + # tracker_web_base should be the same as self.base_url() + tracker_web_base = "http://localhost:%d" % tracker_port dirname = '_test_instance' backend = 'anydbm' @@ -106,7 +117,7 @@ def setup_class(cls): password=password.Password('sekrit'), address='fred@example.com') # set the url the test instance will run at. - cls.db.config['TRACKER_WEB'] = "http://localhost:9001/" + cls.db.config['TRACKER_WEB'] = cls.tracker_web # set up mailhost so errors get reported to debuging capture file cls.db.config.MAILHOST = "localhost" cls.db.config.MAIL_HOST = "localhost" @@ -190,8 +201,9 @@ class ClientSetup(): def create_login_session(self, username="admin", password="sekrit", return_response=True, expect_login_ok=True): # Set up session to manage cookies + session = requests.Session() - session.headers.update({'Origin': 'http://localhost:9001'}) + session.headers.update({'Origin': self.tracker_web_base}) # login using form to get cookie login = {"__login_name": username, '__login_password': password, @@ -747,13 +759,13 @@ def test_rest_endpoint_root_options(self): f = requests.options(self.url_base() + '/rest', auth=('admin', 'sekrit'), headers = {'content-type': "", - 'Origin': "http://localhost:9001", + 'Origin': self.tracker_web_base, }) print(f.status_code) print(f.headers) self.assertEqual(f.status_code, 204) - expected = { 'Access-Control-Allow-Origin': 'http://localhost:9001', + expected = { 'Access-Control-Allow-Origin': self.tracker_web_base, 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', 'Allow': 'OPTIONS, GET', 'Access-Control-Allow-Credentials': 'true', @@ -770,13 +782,13 @@ def test_rest_endpoint_data_options(self): f = requests.options(self.url_base() + '/rest/data', auth=('admin', 'sekrit'), headers = {'content-type': "", - 'Origin': "http://localhost:9001", + 'Origin': self.tracker_web_base, }) print(f.status_code) print(f.headers) self.assertEqual(f.status_code, 204) - expected = { 'Access-Control-Allow-Origin': 'http://localhost:9001', + expected = { 'Access-Control-Allow-Origin': self.tracker_web_base, 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', 'Allow': 'OPTIONS, GET', 'Access-Control-Allow-Methods': 'OPTIONS, GET', @@ -792,13 +804,13 @@ def test_rest_endpoint_collection_options(self): f = requests.options(self.url_base() + '/rest/data/user', auth=('admin', 'sekrit'), headers = {'content-type': "", - 'Origin': "http://localhost:9001", + 'Origin': self.tracker_web_base, }) print(f.status_code) print(f.headers) self.assertEqual(f.status_code, 204) - expected = { 'Access-Control-Allow-Origin': 'http://localhost:9001', + expected = { 'Access-Control-Allow-Origin': self.tracker_web_base, 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', 'Allow': 'OPTIONS, GET, POST', 'Access-Control-Allow-Methods': 'OPTIONS, GET, POST', @@ -815,13 +827,13 @@ def test_rest_endpoint_item_options(self): f = requests.options(self.url_base() + '/rest/data/user/1', auth=('admin', 'sekrit'), headers = {'content-type': "", - 'Origin': "http://localhost:9001", + 'Origin': self.tracker_web_base, }) print(f.status_code) print(f.headers) self.assertEqual(f.status_code, 204) - expected = { 'Access-Control-Allow-Origin': 'http://localhost:9001', + expected = { 'Access-Control-Allow-Origin': self.tracker_web_base, 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', 'Allow': 'OPTIONS, GET, PUT, DELETE, PATCH', 'Access-Control-Allow-Methods': 'OPTIONS, GET, PUT, DELETE, PATCH', @@ -837,13 +849,13 @@ def test_rest_endpoint_attribute_options(self): f = requests.options(self.url_base() + '/rest/data/user/1/username', auth=('admin', 'sekrit'), headers = {'content-type': "", - 'Origin': "http://localhost:9001", + 'Origin': self.tracker_web_base, }) print(f.status_code) print(f.headers) self.assertEqual(f.status_code, 204) - expected = { 'Access-Control-Allow-Origin': 'http://localhost:9001', + expected = { 'Access-Control-Allow-Origin': self.tracker_web_base, 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, X-HTTP-Method-Override', 'Allow': 'OPTIONS, GET, PUT, DELETE, PATCH', 'Access-Control-Allow-Methods': 'OPTIONS, GET, PUT, DELETE, PATCH', @@ -859,7 +871,7 @@ def test_rest_endpoint_attribute_options(self): f = requests.options(self.url_base() + '/rest/data/user/1/creator', auth=('admin', 'sekrit'), headers = {'content-type': "", - 'Origin': "http://localhost:9001", + 'Origin': self.tracker_web_base, }) print(f.status_code) print(f.headers) @@ -877,7 +889,7 @@ def test_rest_endpoint_attribute_options(self): f = requests.options(self.url_base() + '/rest/data/user/1/zot', auth=('admin', 'sekrit'), headers = {'content-type': "", - 'Origin': "http://localhost:9001",}) + 'Origin': self.tracker_web_base,}) print(f.status_code) print(f.headers) @@ -888,7 +900,7 @@ def test_rest_endpoint_user_roles(self): f = requests.get(self.url_base() + '/rest/data/user/roles', auth=('admin', 'sekrit'), headers = {'content-type': "", - 'Origin': "http://localhost:9001", + 'Origin': self.tracker_web_base, }) print(f.status_code) print(f.headers) @@ -1190,10 +1202,10 @@ def test_compression_none_etag(self): content_str = '''{ "data": { "id": "1", - "link": "http://localhost:9001/rest/data/user/1/username", + "link": "%s/rest/data/user/1/username", "data": "admin" } - }''' + }''' % self.tracker_web_base content = json.loads(content_str) @@ -1248,10 +1260,10 @@ def test_compression_gzip(self, method='gzip'): content_str = '''{ "data": { "id": "1", - "link": "http://localhost:9001/rest/data/user/1/username", + "link": "%s/rest/data/user/1/username", "data": "admin" } - }''' + }''' % self.tracker_web_base content = json.loads(content_str) print(f.content) @@ -1496,7 +1508,7 @@ def test_new_issue_with_file_upload(self): # Escape % signs in string by doubling them. This verifies the # search is working correctly. # use groupdict for python2. - self.assertEqual('http://localhost:9001/issue%(issue)s?@ok_message=file%%20%(file)s%%20created%%0Aissue%%20%(issue)s%%20created&@template=item'%m.groupdict(), f.url) + self.assertEqual( self.tracker_web_base + '/issue%(issue)s?@ok_message=file%%20%(file)s%%20created%%0Aissue%%20%(issue)s%%20created&@template=item'%m.groupdict(), f.url) # we have an issue display, verify filename is listed there # seach for unique filename given to it. @@ -1520,13 +1532,13 @@ def test_new_file_via_rest(self): c = dict (content = r'xyzzy') r = session.post(url + 'file', files = c, data = d, headers = {'x-requested-with': "rest", - 'Origin': "http://localhost:9001"} + 'Origin': self.tracker_web_base} ) # was a 500 before fix for issue2551178 self.assertEqual(r.status_code, 201) # just compare the path leave off the number - self.assertIn('http://localhost:9001/rest/data/file/', + self.assertIn(self.tracker_web_base + '/rest/data/file/', r.headers["location"]) json_dict = json.loads(r.text) self.assertEqual(json_dict["data"]["link"], r.headers["location"]) @@ -1534,7 +1546,7 @@ def test_new_file_via_rest(self): # download file and verify content r = session.get(r.headers["location"] +'/content', headers = {'x-requested-with': "rest", - 'Origin': "http://localhost:9001"} + 'Origin': self.tracker_web_base} ) json_dict = json.loads(r.text) self.assertEqual(json_dict['data']['data'], c["content"]) @@ -1544,7 +1556,7 @@ def test_new_file_via_rest(self): session.auth = None r = session.post(url + 'file', files = c, data = d, headers = {'x-requested-with': "rest", - 'Origin': "http://localhost:9001"} + 'Origin': self.tracker_web_base} ) self.assertEqual(r.status_code, 403) @@ -1556,7 +1568,7 @@ def test_new_file_via_rest(self): r = session.post(url + 'file', files = c, data = d, headers = {'x-requested-with': "rest", - 'Origin': "http://localhost:9001"} + 'Origin': self.tracker_web_base} ) self.assertEqual(r.status_code, 201) print(r.status_code) @@ -1611,7 +1623,7 @@ def setup_class(cls): password=password.Password('sekrit'), address='fred@example.com') # set the url the test instance will run at. - cls.db.config['TRACKER_WEB'] = "http://localhost:9001/" + cls.db.config['TRACKER_WEB'] = cls.tracker_web # set up mailhost so errors get reported to debuging capture file cls.db.config.MAILHOST = "localhost" cls.db.config.MAIL_HOST = "localhost" @@ -1699,7 +1711,7 @@ def setup_class(cls): password=password.Password('sekrit'), address='fred@example.com') # set the url the test instance will run at. - cls.db.config['TRACKER_WEB'] = "http://localhost:9001/" + cls.db.config['TRACKER_WEB'] = cls.tracker_web # set up mailhost so errors get reported to debuging capture file cls.db.config.MAILHOST = "localhost" cls.db.config.MAIL_HOST = "localhost" @@ -1761,7 +1773,7 @@ def test_rest_login_RateLimit(self): # use basic auth for rest endpoint request_headers = {'content-type': "", - 'Origin': "http://localhost:9001",} + 'Origin': self.tracker_web_base,} f = requests.options(url_base_numeric + '/rest/data', auth=('admin', 'sekrit'), headers=request_headers @@ -1798,7 +1810,7 @@ def test_rest_login_RateLimit(self): f = requests.options(url_base_numeric + '/rest/data', auth=('admin', 'ekrit'), headers = {'content-type': "", - 'Origin': "http://localhost:9001",} + 'Origin': self.tracker_web_base,} ) if (i < 4): # assuming limit is 4. @@ -1835,7 +1847,7 @@ def test_rest_login_RateLimit(self): f = requests.options(url_base_numeric + '/rest/data', auth=('admin', 'sekrit'), headers = {'content-type': "", - 'Origin': "http://localhost:9001",} + 'Origin': self.tracker_web_base,} ) self.assertEqual(f.status_code, 429) @@ -1850,7 +1862,7 @@ def test_rest_login_RateLimit(self): f = requests.get(url_base_numeric + '/rest/data', auth=('admin', 'sekrit'), headers = {'content-type': "", - 'Origin': "http://localhost:9001",} + 'Origin': self.tracker_web_base,} ) self.assertEqual(f.status_code, 200) print(i, f.status_code) @@ -1868,7 +1880,7 @@ def test_rest_login_RateLimit(self): 'Retry-After, ' 'Sunset, ' 'Allow'), - 'Access-Control-Allow-Origin': 'http://localhost:9001', + 'Access-Control-Allow-Origin': self.tracker_web_base, 'Access-Control-Allow-Credentials': 'true', 'Allow': 'OPTIONS, GET, POST, PUT, DELETE, PATCH' } @@ -1879,28 +1891,28 @@ def test_rest_login_RateLimit(self): expected_data = { "status": { - "link": "http://localhost:9001/rest/data/status" + "link": self.tracker_web_base + "/rest/data/status" }, "keyword": { - "link": "http://localhost:9001/rest/data/keyword" + "link": self.tracker_web_base + "/rest/data/keyword" }, "priority": { - "link": "http://localhost:9001/rest/data/priority" + "link": self.tracker_web_base + "/rest/data/priority" }, "user": { - "link": "http://localhost:9001/rest/data/user" + "link": self.tracker_web_base + "/rest/data/user" }, "file": { - "link": "http://localhost:9001/rest/data/file" + "link": self.tracker_web_base + "/rest/data/file" }, "msg": { - "link": "http://localhost:9001/rest/data/msg" + "link": self.tracker_web_base + "/rest/data/msg" }, "query": { - "link": "http://localhost:9001/rest/data/query" + "link": self.tracker_web_base + "/rest/data/query" }, "issue": { - "link": "http://localhost:9001/rest/data/issue" + "link": self.tracker_web_base + "/rest/data/issue" } } diff --git a/test/wsgi_liveserver.py b/test/wsgi_liveserver.py index f444ba35..93e9060a 100644 --- a/test/wsgi_liveserver.py +++ b/test/wsgi_liveserver.py @@ -9,8 +9,9 @@ Copyright (c) 2013 John Kristensen (unless explicitly stated otherwise). """ -import threading +import errno import socket +import threading import unittest from wsgiref.simple_server import make_server, WSGIRequestHandler @@ -82,3 +83,19 @@ def _post_teardown(self): self._server.server_close() self._thread.join() del self._server + + def probe_ports(start=port_range[0], end=port_range[1]): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + port = start + try: + s.connect(('127.0.0.1', port)) + except socket.error as e: + if not hasattr(e, 'args') or e.args[0] != errno.ECONNREFUSED: + raise + return port + else: + s.close() + port += 1 + if port > end: + return None