Skip to content

Commit d901c83

Browse files
committed
use 'port-for' for port generation
1 parent f119bc2 commit d901c83

File tree

4 files changed

+121
-108
lines changed

4 files changed

+121
-108
lines changed

Dockerfile.tmpl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ FROM postgres:${PG_VERSION}-alpine
33
ENV PYTHON=python${PYTHON_VERSION}
44
RUN if [ "${PYTHON_VERSION}" = "2" ] ; then \
55
apk add --no-cache python py-pip; \
6-
pip install six pg8000 flake8; \
6+
pip install six pg8000 flake8 port-for; \
77
fi
88
RUN if [ "${PYTHON_VERSION}" = "3" ] ; then \
99
apk add --no-cache python3; \
10-
pip3 install six pg8000 flake8; \
10+
pip3 install six pg8000 flake8 port-for; \
1111
fi
1212
ENV LANG=C.UTF-8
1313

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
setup(
33
name='testgres',
44
packages=['testgres'],
5-
version='0.3.1',
5+
version='0.4.0',
66
description='Testing utility for postgresql and its extensions',
77
license='PostgreSQL',
88
author='Ildar Musin',
99
author_email='[email protected]',
1010
url='https://github.com/postgrespro/testgres',
1111
keywords=['testing', 'postgresql'],
1212
classifiers=[],
13-
install_requires=["pg8000", "six"]
13+
install_requires=["pg8000", "six", "port-for"]
1414
)

testgres/testgres.py

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,13 @@
2222
"""
2323

2424
import os
25-
import random
26-
# import socket
2725
import subprocess
2826
import pwd
2927
import tempfile
3028
import shutil
3129
import time
3230
import six
31+
import port_for
3332

3433
import threading
3534
import logging
@@ -48,10 +47,10 @@
4847
raise ImportError("You must have psycopg2 or pg8000 modules installed")
4948

5049

50+
bound_ports = set()
5151
registered_nodes = []
5252
util_threads = []
5353
tmp_dirs = []
54-
last_assigned_port = int(random.random() * 16384) + 49152
5554
pg_config_data = {}
5655
base_data_dir = None
5756

@@ -72,6 +71,14 @@ class QueryException(Exception):
7271
pass
7372

7473

74+
class InitPostgresNodeException(Exception):
75+
76+
"""
77+
Predefined exceptions
78+
"""
79+
pass
80+
81+
7582
class PgcontroldataException(Exception):
7683
def __init__(self, error_text, cmd):
7784
self.error_text = error_text
@@ -205,6 +212,16 @@ def close(self):
205212
class PostgresNode(object):
206213

207214
def __init__(self, name, port, base_dir=None, use_logging=False):
215+
global bound_ports
216+
217+
# check that port is not used
218+
if port in bound_ports:
219+
raise InitPostgresNodeException(
220+
'port {} is already in use'.format(port))
221+
222+
# mark port as used
223+
bound_ports.add(port)
224+
208225
self.name = name
209226
self.host = '127.0.0.1'
210227
self.port = port
@@ -220,6 +237,18 @@ def __init__(self, name, port, base_dir=None, use_logging=False):
220237
self.use_logging = use_logging
221238
self.logger = None
222239

240+
def __enter__(self):
241+
return self
242+
243+
def __exit__(self, type, value, traceback):
244+
global bound_ports
245+
246+
# stop node if necessary
247+
self.cleanup()
248+
249+
# mark port as free
250+
bound_ports.remove(self.port)
251+
223252
@property
224253
def data_dir(self):
225254
return os.path.join(self.base_dir, "data")
@@ -718,31 +747,12 @@ def version_to_num(version):
718747

719748
def get_new_node(name, base_dir=None, use_logging=False):
720749
global registered_nodes
721-
global last_assigned_port
722-
723-
port = last_assigned_port + 1
724-
725-
# TODO: check if port is already used
726-
727-
# found = False
728-
# while found:
729-
# # Check first that candidate port number is not included in
730-
# # the list of already-registered nodes.
731-
# found = True
732-
# for node in registered_nodes:
733-
# if node.port == port:
734-
# found = False
735-
# break
736750

737-
# if found:
738-
# socket(socket.SOCK,
739-
# socket.PF_INET,
740-
# socket.SOCK_STREAM,
741-
# socket.getprotobyname("tcp"))
751+
# generate a new unique port
752+
port = port_for.select_random(exclude_ports=bound_ports)
742753

743754
node = PostgresNode(name, port, base_dir, use_logging=use_logging)
744755
registered_nodes.append(node)
745-
last_assigned_port = port
746756

747757
return node
748758

testgres/tests/test_simple.py

Lines changed: 83 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import tempfile
77
import logging.config
88

9-
from testgres import get_new_node, stop_all, get_config, clean_all
9+
from testgres import get_new_node, stop_all, get_config, clean_all, bound_ports
1010

1111

1212
class SimpleTest(unittest.TestCase):
@@ -17,79 +17,70 @@ def teardown(self):
1717

1818
@unittest.skip("demo")
1919
def test_start_stop(self):
20-
node = get_new_node('test')
21-
node.init()
22-
node.start()
23-
res = node.execute('postgres', 'select 1')
24-
self.assertEqual(len(res), 1)
25-
self.assertEqual(res[0][0], 1)
26-
node.stop()
20+
with get_new_node('test') as node:
21+
node.init().start()
22+
res = node.execute('postgres', 'select 1')
23+
self.assertEqual(len(res), 1)
24+
self.assertEqual(res[0][0], 1)
2725

2826
def test_backup_and_replication(self):
29-
node = get_new_node('test')
30-
replica = get_new_node('repl')
31-
32-
node.init(allows_streaming=True)
33-
node.start()
34-
node.psql('postgres', 'create table abc(a int, b int)')
35-
node.psql('postgres', 'insert into abc values (1, 2)')
36-
node.backup('my_backup')
37-
38-
replica.init_from_backup(node, 'my_backup', has_streaming=True)
39-
replica.start()
40-
res = replica.execute('postgres', 'select * from abc')
41-
self.assertEqual(len(res), 1)
42-
self.assertEqual(res[0][0], 1)
43-
self.assertEqual(res[0][1], 2)
44-
45-
# Prepare the query which would check whether record reached replica
46-
# (It is slightly different for Postgres 9.6 and Postgres 10+)
47-
if get_config()['VERSION_NUM'] >= 1000000:
48-
wait_lsn = 'SELECT pg_current_wal_lsn() <= replay_lsn ' \
49-
'FROM pg_stat_replication WHERE application_name = \'%s\'' \
50-
% replica.name
51-
else:
52-
wait_lsn = 'SELECT pg_current_xlog_location() <= replay_location '\
53-
'FROM pg_stat_replication WHERE application_name = \'%s\'' \
54-
% replica.name
55-
56-
# Insert into master node
57-
node.psql('postgres', 'insert into abc values (3, 4)')
58-
# Wait until data syncronizes
59-
node.poll_query_until('postgres', wait_lsn)
60-
# Check that this record was exported to replica
61-
res = replica.execute('postgres', 'select * from abc')
62-
self.assertEqual(len(res), 2)
63-
self.assertEqual(res[1][0], 3)
64-
self.assertEqual(res[1][1], 4)
65-
66-
node.stop()
67-
replica.stop()
27+
with get_new_node('test') as node, get_new_node('repl') as replica:
28+
node.init(allows_streaming=True)
29+
node.start()
30+
node.psql('postgres', 'create table abc(a int, b int)')
31+
node.psql('postgres', 'insert into abc values (1, 2)')
32+
node.backup('my_backup')
33+
34+
replica.init_from_backup(node, 'my_backup', has_streaming=True)
35+
replica.start()
36+
res = replica.execute('postgres', 'select * from abc')
37+
self.assertEqual(len(res), 1)
38+
self.assertEqual(res[0][0], 1)
39+
self.assertEqual(res[0][1], 2)
40+
41+
# Prepare the query which would check whether record reached replica
42+
# (It is slightly different for Postgres 9.6 and Postgres 10+)
43+
if get_config()['VERSION_NUM'] >= 1000000:
44+
wait_lsn = 'SELECT pg_current_wal_lsn() <= replay_lsn ' \
45+
'FROM pg_stat_replication WHERE application_name = \'%s\'' \
46+
% replica.name
47+
else:
48+
wait_lsn = 'SELECT pg_current_xlog_location() <= replay_location '\
49+
'FROM pg_stat_replication WHERE application_name = \'%s\'' \
50+
% replica.name
51+
52+
# Insert into master node
53+
node.psql('postgres', 'insert into abc values (3, 4)')
54+
# Wait until data syncronizes
55+
node.poll_query_until('postgres', wait_lsn)
56+
# Check that this record was exported to replica
57+
res = replica.execute('postgres', 'select * from abc')
58+
self.assertEqual(len(res), 2)
59+
self.assertEqual(res[1][0], 3)
60+
self.assertEqual(res[1][1], 4)
6861

6962
def test_dump(self):
70-
node = get_new_node('test')
71-
node.init().start()
72-
node.safe_psql(
73-
'postgres',
74-
'create table abc as '
75-
'select g as a, g as b from generate_series(1, 10) as g'
76-
)
77-
node.psql('postgres', 'create database test')
78-
node.dump('postgres', 'test.sql')
79-
node.restore('test', 'test.sql')
80-
self.assertEqual(
81-
node.psql('postgres', 'select * from abc'),
82-
node.psql('test', 'select * from abc'),
83-
)
84-
node.stop()
63+
with get_new_node('test') as node:
64+
node.init().start()
65+
node.safe_psql(
66+
'postgres',
67+
'create table abc as '
68+
'select g as a, g as b from generate_series(1, 10) as g'
69+
)
70+
node.psql('postgres', 'create database test')
71+
node.dump('postgres', 'test.sql')
72+
node.restore('test', 'test.sql')
73+
self.assertEqual(
74+
node.psql('postgres', 'select * from abc'),
75+
node.psql('test', 'select * from abc'),
76+
)
8577

8678
def test_users(self):
87-
node = get_new_node('master')
88-
node.init().start()
89-
node.psql('postgres', 'create role test_user login')
90-
value = node.safe_psql('postgres', 'select 1', username='test_user')
91-
self.assertEqual(value, six.b('1\n'))
92-
node.stop()
79+
with get_new_node('master') as node:
80+
node.init().start()
81+
node.psql('postgres', 'create role test_user login')
82+
value = node.safe_psql('postgres', 'select 1', username='test_user')
83+
self.assertEqual(value, six.b('1\n'))
9384

9485
def test_logging(self):
9586
regex = re.compile('.+?LOG:.*')
@@ -118,21 +109,33 @@ def test_logging(self):
118109

119110
logging.config.dictConfig(log_conf)
120111

121-
node = get_new_node('master', use_logging=True)
122-
node1 = get_new_node('slave1', use_logging=True)
123-
node2 = get_new_node('slave2', use_logging=True)
112+
with get_new_node('master', use_logging=True) as node, \
113+
get_new_node('slave1', use_logging=True) as node1, \
114+
get_new_node('slave2', use_logging=True) as node2:
124115

125-
node.init().start()
126-
node1.init().start()
127-
node2.init().start()
116+
node.init().start()
117+
node1.init().start()
118+
node2.init().start()
128119

129-
with open(logfile.name, 'r') as log:
130-
for line in log:
131-
self.assertTrue(regex.match(line))
120+
with open(logfile.name, 'r') as log:
121+
for line in log:
122+
self.assertTrue(regex.match(line))
132123

133-
node.stop()
134-
node1.stop()
135-
node2.stop()
124+
def test_ports_management(self):
125+
# check that no ports have been bound yet
126+
self.assertEqual(len(bound_ports), 0)
127+
128+
with get_new_node('node') as node:
129+
# check that we've just bound a port
130+
self.assertEqual(len(bound_ports), 1)
131+
132+
# check that bound_ports contains our port
133+
port_1 = list(bound_ports)[0]
134+
port_2 = node.port
135+
self.assertEqual(port_1, port_2)
136+
137+
# check that port has been freed successfuly
138+
self.assertEqual(len(bound_ports), 0)
136139

137140

138141
if __name__ == '__main__':

0 commit comments

Comments
 (0)