Merge lp:~cjwatson/storm/postgresfixture into lp:storm

Proposed by Colin Watson
Status: Merged
Merged at revision: 536
Proposed branch: lp:~cjwatson/storm/postgresfixture
Merge into: lp:storm
Diff against target: 425 lines (+125/-60)
12 files modified
.bzrignore (+2/-0)
MANIFEST.in (+2/-0)
NEWS (+6/-0)
README (+0/-1)
dev/db-setup (+0/-45)
setup.py (+1/-0)
storm/tests/databases/base.py (+23/-8)
storm/tests/databases/postgres.py (+33/-0)
storm/tests/databases/proxy.py (+18/-3)
storm/tests/mocker.py (+1/-1)
test (+39/-0)
tox.ini (+0/-2)
To merge this branch: bzr merge lp:~cjwatson/storm/postgresfixture
Reviewer Review Type Date Requested Status
Simon Poirier (community) Approve
Review via email: mp+373379@code.launchpad.net

Commit message

Use postgresfixture to simplify developer setup.

Description of the change

Since postgresfixture provides a cluster listening on a Unix-domain socket, we use a TCP proxy for tests that require a TCP connection string.

This necessitated some minor improvements to test cleanup code to ensure that the temporary database can be dropped cleanly.

To post a comment you must log in.
Revision history for this message
Simon Poirier (simpoir) wrote :

+1 LGTM

review: Approve
Revision history for this message
Colin Watson (cjwatson) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.bzrignore'
--- .bzrignore 2019-09-17 09:45:08 +0000
+++ .bzrignore 2019-09-29 14:08:50 +0000
@@ -2,6 +2,8 @@
2build2build
3storm.egg-info3storm.egg-info
4build-stamp4build-stamp
5db
6.db.lock
5dist7dist
6debian/files8debian/files
7debian/python-storm9debian/python-storm
810
=== modified file 'MANIFEST.in'
--- MANIFEST.in 2019-08-11 17:51:37 +0000
+++ MANIFEST.in 2019-09-29 14:08:50 +0000
@@ -1,3 +1,5 @@
1recursive-include storm *.py *.c *.zcml *.txt1recursive-include storm *.py *.c *.zcml *.txt
22
3include MANIFEST.in LICENSE README TODO NEWS Makefile setup.cfg test tox.ini3include MANIFEST.in LICENSE README TODO NEWS Makefile setup.cfg test tox.ini
4
5prune db
46
=== modified file 'NEWS'
--- NEWS 2019-09-27 19:22:54 +0000
+++ NEWS 2019-09-29 14:08:50 +0000
@@ -1,6 +1,12 @@
10.21.1 (XXXX-XX-XX)10.21.1 (XXXX-XX-XX)
2===================2===================
33
4Improvements
5------------
6
7- Use the postgresfixture package to set up a temporary cluster for
8 PostgreSQL tests, simplifying developer setup.
9
4Bug fixes10Bug fixes
5---------11---------
612
713
=== modified file 'README'
--- README 2017-02-08 11:47:02 +0000
+++ README 2019-09-29 14:08:50 +0000
@@ -90,7 +90,6 @@
9090
91 $ dev/ubuntu-deps91 $ dev/ubuntu-deps
92 $ make develop92 $ make develop
93 $ dev/db-setup
94 $ make check93 $ make check
9594
96LONG VERSION:95LONG VERSION:
9796
=== removed file 'dev/db-setup'
--- dev/db-setup 2016-03-10 12:04:01 +0000
+++ dev/db-setup 1970-01-01 00:00:00 +0000
@@ -1,45 +0,0 @@
1#!/bin/bash -eu
2
3PGHBA=/etc/postgresql/*/main/pg_hba.conf
4PGCONF=/etc/postgresql/*/main/postgresql.conf
5PGINIT=/etc/init.d/postgresql*
6
7is_number() {
8 test "$1" && printf '%f' "$1" >/dev/null;
9}
10
11# Restart service and examine output to attempt to adjust kernel tunables
12restart_service() {
13 sudo $PGINIT stop
14 set +e
15 output=$(sudo $PGINIT start 2>&1)
16 rc=$?
17 set -e
18 if [ $rc -ne 0 ]; then
19 size=$(echo $output | grep -E 'size=[0-9]+' | sed -r 's/.*size=([0-9]+).*/\1/')
20 is_number $size
21 SYSCTLCONF=/etc/sysctl.d/90-storm.conf
22 sudo sh -c "echo kernel.shmmax = $size > $SYSCTLCONF"
23 sudo sysctl -p $SYSCTLCONF
24 sudo $PGINIT start
25 fi
26}
27
28echo " * Fixing localhost access to postgres, restarting service"
29sudo sed -i.bak -r 's/(host.*127\.0\.0\.1.*)\s+\w+$/\1 trust/' $PGHBA
30sudo sed -i.bak -r 's/(host.*::1\/128.*)\s+\w+$/\1 trust/' $PGHBA
31sudo sed -i.bak -r 's/(host.*::1\/128.*)\s+\w+$/\1 trust/' $PGHBA
32sudo sed -i.bak -r 's/#(max_prepared_transactions.*)= 0/\1 = 200/' $PGCONF
33restart_service
34
35echo " * Create postgres superuser ($USER - will fail if existing)"
36# If this fails, we will get errors later, so don't fail.
37sudo -u postgres createuser --superuser $USER || /bin/true
38
39echo " * Create DB Test Fixtures (will fail if existing)"
40createdb storm_test || /bin/true
41
42echo " * Testing DB Access for user:$USER"
43pg_dump storm_test > /dev/null
44
45echo " * All Done."
460
=== modified file 'setup.py'
--- setup.py 2019-09-28 13:03:31 +0000
+++ setup.py 2019-09-29 14:08:50 +0000
@@ -24,6 +24,7 @@
24 "fixtures >= 0.3.5",24 "fixtures >= 0.3.5",
25 # pgbouncer (the Python module) is not yet packaged in Ubuntu.25 # pgbouncer (the Python module) is not yet packaged in Ubuntu.
26 "pgbouncer >= 0.0.7",26 "pgbouncer >= 0.0.7",
27 "postgresfixture",
27 "psycopg2 >= 2.3.0",28 "psycopg2 >= 2.3.0",
28 "testresources >= 0.2.4",29 "testresources >= 0.2.4",
29 "testtools >= 0.9.8",30 "testtools >= 0.9.8",
3031
=== modified file 'storm/tests/databases/base.py'
--- storm/tests/databases/base.py 2019-09-27 19:22:54 +0000
+++ storm/tests/databases/base.py 2019-09-29 14:08:50 +0000
@@ -700,6 +700,7 @@
700 self.create_connection()700 self.create_connection()
701701
702 def tearDown(self):702 def tearDown(self):
703 self.drop_connection()
703 self.drop_database()704 self.drop_database()
704 self.proxy.close()705 self.proxy.close()
705 super(DatabaseDisconnectionMixin, self).tearDown()706 super(DatabaseDisconnectionMixin, self).tearDown()
@@ -708,18 +709,21 @@
708 return bool(self.get_uri())709 return bool(self.get_uri())
709710
710 def get_uri(self):711 def get_uri(self):
711 """Return URI instance with a defined host and port."""712 """Return URI instance with a defined host (and port, for TCP)."""
712 if not self.environment_variable or not self.default_port:713 if not self.environment_variable:
713 raise RuntimeError("Define at least %s.environment_variable and "714 raise RuntimeError(
714 "%s.default_port" % (type(self).__name__,715 "Define at least %s.environment_variable" %
715 type(self).__name__))716 type(self).__name__)
716 uri_str = os.environ.get(self.host_environment_variable)717 uri_str = os.environ.get(self.host_environment_variable)
717 if uri_str:718 if uri_str:
718 uri = URI(uri_str)719 uri = URI(uri_str)
719 if not uri.host:720 if not uri.host:
720 raise RuntimeError("The URI in %s must include a host." %721 raise RuntimeError("The URI in %s must include a host." %
721 self.host_environment_variable)722 self.host_environment_variable)
722 if not uri.port:723 if not uri.host.startswith("/") and not uri.port:
724 if not self.default_port:
725 raise RuntimeError(
726 "Define at least %s.default_port" % type(self).__name)
723 uri.port = self.default_port727 uri.port = self.default_port
724 return uri728 return uri
725 else:729 else:
@@ -727,11 +731,19 @@
727 if uri_str:731 if uri_str:
728 uri = URI(uri_str)732 uri = URI(uri_str)
729 if uri.host:733 if uri.host:
730 if not uri.port:734 if not uri.host.startswith("/") and not uri.port:
735 if not self.default_port:
736 raise RuntimeError(
737 "Define at least %s.default_port" %
738 type(self).__name)
731 uri.port = self.default_port739 uri.port = self.default_port
732 return uri740 return uri
733 return None741 return None
734742
743 def create_proxy(self, uri):
744 """Create a TCP proxy forwarding requests to `uri`."""
745 return ProxyTCPServer((uri.host, uri.port))
746
735 def create_database_and_proxy(self):747 def create_database_and_proxy(self):
736 """Set up the TCP proxy and database object.748 """Set up the TCP proxy and database object.
737749
@@ -739,7 +751,7 @@
739 database object should point at the TCP proxy.751 database object should point at the TCP proxy.
740 """752 """
741 uri = self.get_uri()753 uri = self.get_uri()
742 self.proxy = ProxyTCPServer((uri.host, uri.port))754 self.proxy = self.create_proxy(uri)
743 uri.host, uri.port = self.proxy.server_address755 uri.host, uri.port = self.proxy.server_address
744 self.proxy_uri = uri756 self.proxy_uri = uri
745 self.database = create_database(uri)757 self.database = create_database(uri)
@@ -747,6 +759,9 @@
747 def create_connection(self):759 def create_connection(self):
748 self.connection = self.database.connect()760 self.connection = self.database.connect()
749761
762 def drop_connection(self):
763 self.connection.close()
764
750 def drop_database(self):765 def drop_database(self):
751 pass766 pass
752767
753768
=== modified file 'storm/tests/databases/postgres.py'
--- storm/tests/databases/postgres.py 2019-09-17 09:35:10 +0000
+++ storm/tests/databases/postgres.py 2019-09-29 14:08:50 +0000
@@ -25,6 +25,7 @@
25import json25import json
2626
27import six27import six
28from six.moves.urllib.parse import urlunsplit
2829
29from storm.databases.postgres import (30from storm.databases.postgres import (
30 Postgres, compile, currval, Returning, Case, PostgresTimeoutTracer,31 Postgres, compile, currval, Returning, Case, PostgresTimeoutTracer,
@@ -50,6 +51,7 @@
50from storm.tests.databases.base import (51from storm.tests.databases.base import (
51 DatabaseTest, DatabaseDisconnectionTest, UnsupportedDatabaseTest,52 DatabaseTest, DatabaseDisconnectionTest, UnsupportedDatabaseTest,
52 TwoPhaseCommitTest, TwoPhaseCommitDisconnectionTest)53 TwoPhaseCommitTest, TwoPhaseCommitDisconnectionTest)
54from storm.tests.databases.proxy import ProxyTCPServer
53from storm.tests.expr import column1, column2, column3, elem1, table1, TrackContext55from storm.tests.expr import column1, column2, column3, elem1, table1, TrackContext
54from storm.tests.tracer import TimeoutTracerTestBase56from storm.tests.tracer import TimeoutTracerTestBase
55from storm.tests.helper import TestHelper57from storm.tests.helper import TestHelper
@@ -62,6 +64,16 @@
62 has_pgbouncer = True64 has_pgbouncer = True
6365
6466
67def create_proxy_and_uri(uri):
68 """Create a TCP proxy to a Unix-domain database identified by `uri`."""
69 proxy = ProxyTCPServer(os.path.join(uri.host, ".s.PGSQL.5432"))
70 proxy_host, proxy_port = proxy.server_address
71 proxy_uri = URI(urlunsplit(
72 ("postgres", "%s:%s" % (proxy_host, proxy_port), "/storm_test",
73 "", "")))
74 return proxy, proxy_uri
75
76
65def terminate_other_backends(connection):77def terminate_other_backends(connection):
66 """Terminate all connections to the database except the one given."""78 """Terminate all connections to the database except the one given."""
67 pid_column = "procpid" if connection._database._version < 90200 else "pid"79 pid_column = "procpid" if connection._database._version < 90200 else "pid"
@@ -737,6 +749,13 @@
737 host_environment_variable = "STORM_POSTGRES_HOST_URI"749 host_environment_variable = "STORM_POSTGRES_HOST_URI"
738 default_port = 5432750 default_port = 5432
739751
752 def create_proxy(self, uri):
753 """See `DatabaseDisconnectionMixin.create_proxy`."""
754 if uri.host.startswith("/"):
755 return create_proxy_and_uri(uri)[0]
756 else:
757 return super(PostgresDisconnectionTest, self).create_proxy(uri)
758
740 def test_rollback_swallows_InterfaceError(self):759 def test_rollback_swallows_InterfaceError(self):
741 """Test that InterfaceErrors get caught on rollback().760 """Test that InterfaceErrors get caught on rollback().
742761
@@ -803,6 +822,13 @@
803822
804 database_uri = os.environ.get("STORM_POSTGRES_HOST_URI")823 database_uri = os.environ.get("STORM_POSTGRES_HOST_URI")
805824
825 def setUp(self):
826 super(PostgresDisconnectionTestWithoutProxyTCPSockets, self).setUp()
827 if self.database.get_uri().host.startswith("/"):
828 proxy, proxy_uri = create_proxy_and_uri(self.database.get_uri())
829 self.addCleanup(proxy.close)
830 self.database = create_database(proxy_uri)
831
806832
807class PostgresDisconnectionTestWithPGBouncerBase(object):833class PostgresDisconnectionTestWithPGBouncerBase(object):
808 # Connecting via pgbouncer <http://pgfoundry.org/projects/pgbouncer>834 # Connecting via pgbouncer <http://pgfoundry.org/projects/pgbouncer>
@@ -816,6 +842,9 @@
816 def setUp(self):842 def setUp(self):
817 super(PostgresDisconnectionTestWithPGBouncerBase, self).setUp()843 super(PostgresDisconnectionTestWithPGBouncerBase, self).setUp()
818 database_uri = URI(os.environ["STORM_POSTGRES_HOST_URI"])844 database_uri = URI(os.environ["STORM_POSTGRES_HOST_URI"])
845 if database_uri.host.startswith("/"):
846 proxy, database_uri = create_proxy_and_uri(database_uri)
847 self.addCleanup(proxy.close)
819 database_user = database_uri.username or os.environ['USER']848 database_user = database_uri.username or os.environ['USER']
820 database_dsn = make_dsn(database_uri)849 database_dsn = make_dsn(database_uri)
821 # Create a pgbouncer fixture.850 # Create a pgbouncer fixture.
@@ -874,6 +903,10 @@
874 self.tracer.get_remaining_time = lambda: self.remaining_time903 self.tracer.get_remaining_time = lambda: self.remaining_time
875 self.remaining_time = 10.5904 self.remaining_time = 10.5
876905
906 def tearDown(self):
907 self.connection.close()
908 super(PostgresTimeoutTracerTest, self).tearDown()
909
877 def test_set_statement_timeout(self):910 def test_set_statement_timeout(self):
878 result = self.connection.execute("SHOW statement_timeout")911 result = self.connection.execute("SHOW statement_timeout")
879 self.assertEquals(result.get_one(), ("10500ms",))912 self.assertEquals(result.get_one(), ("10500ms",))
880913
=== modified file 'storm/tests/databases/proxy.py'
--- storm/tests/databases/proxy.py 2019-06-07 17:14:33 +0000
+++ storm/tests/databases/proxy.py 2019-09-29 14:08:50 +0000
@@ -22,11 +22,13 @@
2222
23from __future__ import print_function23from __future__ import print_function
2424
25import errno
25import os26import os
26import select27import select
27import socket28import socket
28import threading29import threading
2930
31import six
30from six.moves import socketserver32from six.moves import socketserver
3133
3234
@@ -42,7 +44,10 @@
42 self, request, client_address, server)44 self, request, client_address, server)
4345
44 def handle(self):46 def handle(self):
45 dst = socket.socket(socket.AF_INET, socket.SOCK_STREAM)47 if isinstance(self.server.proxy_dest, (bytes, six.text_type)):
48 dst = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
49 else:
50 dst = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
46 dst.connect(self.server.proxy_dest)51 dst.connect(self.server.proxy_dest)
4752
48 readers = [self.request, dst]53 readers = [self.request, dst]
@@ -56,13 +61,23 @@
5661
57 if self.request in rlist:62 if self.request in rlist:
58 chunk = os.read(self.request.fileno(), 1024)63 chunk = os.read(self.request.fileno(), 1024)
59 dst.send(chunk)64 try:
65 dst.send(chunk)
66 except socket.error as e:
67 if e.errno == errno.EPIPE:
68 return
69 raise
60 if chunk == "":70 if chunk == "":
61 readers.remove(self.request)71 readers.remove(self.request)
62 dst.shutdown(socket.SHUT_WR)72 dst.shutdown(socket.SHUT_WR)
6373
64 if dst in rlist:74 if dst in rlist:
65 chunk = os.read(dst.fileno(), 1024)75 try:
76 chunk = os.read(dst.fileno(), 1024)
77 except OSError as e:
78 if e.errno == errno.ECONNRESET:
79 return
80 raise
66 self.request.send(chunk)81 self.request.send(chunk)
67 if chunk == "":82 if chunk == "":
68 readers.remove(dst)83 readers.remove(dst)
6984
=== modified file 'storm/tests/mocker.py'
--- storm/tests/mocker.py 2019-09-17 09:35:10 +0000
+++ storm/tests/mocker.py 2019-09-29 14:08:50 +0000
@@ -149,7 +149,7 @@
149 elif os.path.isdir(path):149 elif os.path.isdir(path):
150 shutil.rmtree(path)150 shutil.rmtree(path)
151 self.mocker.restore()151 self.mocker.restore()
152 for func, args, kwargs in self.__cleanup_funcs:152 for func, args, kwargs in reversed(self.__cleanup_funcs):
153 func(*args, **kwargs)153 func(*args, **kwargs)
154154
155 def addCleanup(self, func, *args, **kwargs):155 def addCleanup(self, func, *args, **kwargs):
156156
=== modified file 'test'
--- test 2019-08-11 17:51:37 +0000
+++ test 2019-09-29 14:08:50 +0000
@@ -21,6 +21,7 @@
21#21#
22import glob22import glob
23import optparse23import optparse
24import re
24import unittest25import unittest
25import sys26import sys
26import os27import os
@@ -73,6 +74,43 @@
73 return test_with_runner(runner)74 return test_with_runner(runner)
7475
7576
77def with_postgresfixture(runner_func):
78 """If possible, wrap a test runner with code to set up PostgreSQL."""
79 try:
80 from postgresfixture import ClusterFixture
81 except ImportError:
82 return runner_func
83
84 from six.moves.urllib.parse import urlunsplit
85 from storm.uri import escape
86
87 def wrapper():
88 cluster = ClusterFixture("db")
89 cluster.create()
90 # Allow use of prepared transactions, which some tests need.
91 pg_conf_path = os.path.join(cluster.datadir, "postgresql.conf")
92 with open(pg_conf_path) as pg_conf_old:
93 with open(pg_conf_path + ".new", "w") as pg_conf_new:
94 for line in pg_conf_old:
95 pg_conf_new.write(re.sub(
96 r"^#(max_prepared_transactions.*)= 0", r"\1= 200",
97 line))
98 os.fchmod(
99 pg_conf_new.fileno(),
100 os.fstat(pg_conf_old.fileno()).st_mode)
101 os.rename(pg_conf_path + ".new", pg_conf_path)
102
103 with cluster:
104 cluster.createdb("storm_test")
105 uri = urlunsplit(
106 ("postgres", escape(cluster.datadir), "/storm_test", "", ""))
107 os.environ["STORM_POSTGRES_URI"] = uri
108 os.environ["STORM_POSTGRES_HOST_URI"] = uri
109 return runner_func()
110
111 return wrapper
112
113
76if __name__ == "__main__":114if __name__ == "__main__":
77 runner = os.environ.get("STORM_TEST_RUNNER")115 runner = os.environ.get("STORM_TEST_RUNNER")
78 if not runner:116 if not runner:
@@ -80,6 +118,7 @@
80 runner_func = globals().get("test_with_%s" % runner.replace(".", "_"))118 runner_func = globals().get("test_with_%s" % runner.replace(".", "_"))
81 if not runner_func:119 if not runner_func:
82 sys.exit("Test runner not found: %s" % runner)120 sys.exit("Test runner not found: %s" % runner)
121 runner_func = with_postgresfixture(runner_func)
83 sys.exit(runner_func())122 sys.exit(runner_func())
84123
85# vim:ts=4:sw=4:et124# vim:ts=4:sw=4:et
86125
=== modified file 'tox.ini'
--- tox.ini 2019-09-17 09:45:08 +0000
+++ tox.ini 2019-09-29 14:08:50 +0000
@@ -8,8 +8,6 @@
8 .[test]8 .[test]
9passenv = STORM_TEST_RUNNER USER9passenv = STORM_TEST_RUNNER USER
10setenv =10setenv =
11 STORM_POSTGRES_URI = postgres:storm_test
12 STORM_POSTGRES_HOST_URI = postgres://localhost:5432/storm_test
13 cextensions: STORM_CEXTENSIONS = 111 cextensions: STORM_CEXTENSIONS = 1
14 nocextensions: STORM_CEXTENSIONS = 012 nocextensions: STORM_CEXTENSIONS = 0
15commands =13commands =

Subscribers

People subscribed via source and target branches

to status/vote changes: