Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f8b95c6

Browse files
committed
Merge branch 'master' into logical
2 parents 782484b + e3492bd commit f8b95c6

File tree

9 files changed

+255
-37
lines changed

9 files changed

+255
-37
lines changed

.dockerignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
dist
2+
env
3+
venv
4+
*.egg-info

Dockerfile.tmpl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ FROM postgres:${PG_VERSION}-alpine
22

33
ENV PYTHON=python${PYTHON_VERSION}
44
RUN if [ "${PYTHON_VERSION}" = "2" ] ; then \
5-
apk add --no-cache curl python2 py-virtualenv py-pip; \
5+
apk add --no-cache curl python2 python2-dev build-base musl-dev \
6+
linux-headers py-virtualenv py-pip; \
67
fi
78
RUN if [ "${PYTHON_VERSION}" = "3" ] ; then \
8-
apk add --no-cache curl python3 py-virtualenv; \
9+
apk add --no-cache curl python3 python3-dev build-base musl-dev \
10+
linux-headers py-virtualenv; \
911
fi
1012
ENV LANG=C.UTF-8
1113

run_tests.sh

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export VIRTUAL_ENV_DISABLE_PROMPT=1
2222
source $VENV_PATH/bin/activate
2323

2424
# install utilities
25-
$PIP install coverage flake8
25+
$PIP install coverage flake8 psutil
2626

2727
# install testgres' dependencies
2828
export PYTHONPATH=$(pwd)
@@ -42,15 +42,17 @@ time coverage run -a tests/test_simple.py
4242

4343

4444
# run tests (PG_BIN)
45-
export PG_BIN=$(dirname $(which pg_config))
46-
time coverage run -a tests/test_simple.py
47-
unset PG_BIN
45+
time \
46+
PG_BIN=$(dirname $(which pg_config)) \
47+
ALT_CONFIG=1 \
48+
coverage run -a tests/test_simple.py
4849

4950

5051
# run tests (PG_CONFIG)
51-
export PG_CONFIG=$(which pg_config)
52-
time coverage run -a tests/test_simple.py
53-
unset PG_CONFIG
52+
time \
53+
PG_CONFIG=$(which pg_config) \
54+
ALT_CONFIG=1 \
55+
coverage run -a tests/test_simple.py
5456

5557

5658
# show coverage

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from distutils.core import setup
77

88
# Basic dependencies
9-
install_requires = ["pg8000", "port-for>=0.4", "six>=1.9.0"]
9+
install_requires = ["pg8000", "port-for>=0.4", "six>=1.9.0", "psutil"]
1010

1111
# Add compatibility enum class
1212
if sys.version_info < (3, 4):
@@ -17,7 +17,7 @@
1717
install_requires.append("ipaddress")
1818

1919
setup(
20-
version='1.5.0',
20+
version='1.6.0',
2121
name='testgres',
2222
packages=['testgres'],
2323
description='Testing utility for PostgreSQL and its extensions',

testgres/connection.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ def node(self):
5252
def connection(self):
5353
return self._connection
5454

55+
@property
56+
def pid(self):
57+
return self.execute("select pg_backend_pid()")[0][0]
58+
5559
@property
5660
def cursor(self):
5761
return self._cursor

testgres/enums.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from enum import Enum, IntEnum
2+
from six import iteritems
23

34

45
class XLogMethod(Enum):
@@ -35,3 +36,53 @@ def __bool__(self):
3536

3637
# for Python 2.x
3738
__nonzero__ = __bool__
39+
40+
41+
class ProcessType(Enum):
42+
"""
43+
Types of processes
44+
"""
45+
46+
AutovacuumLauncher = 'autovacuum launcher'
47+
BackgroundWriter = 'background writer'
48+
Checkpointer = 'checkpointer'
49+
LogicalReplicationLauncher = 'logical replication launcher'
50+
Startup = 'startup'
51+
StatsCollector = 'stats collector'
52+
WalReceiver = 'wal receiver'
53+
WalSender = 'wal sender'
54+
WalWriter = 'wal writer'
55+
56+
# special value
57+
Unknown = 'unknown'
58+
59+
@staticmethod
60+
def from_process(process):
61+
# yapf: disable
62+
# legacy names for older releases of PG
63+
alternative_names = {
64+
ProcessType.LogicalReplicationLauncher: [
65+
'logical replication worker'
66+
],
67+
ProcessType.BackgroundWriter: [
68+
'writer'
69+
],
70+
}
71+
72+
# we deliberately cut special words and spaces
73+
cmdline = ''.join(process.cmdline()) \
74+
.replace('postgres:', '', 1) \
75+
.replace('bgworker:', '', 1) \
76+
.replace(' ', '')
77+
78+
for ptype in ProcessType:
79+
if cmdline.startswith(ptype.value.replace(' ', '')):
80+
return ptype
81+
82+
for ptype, names in iteritems(alternative_names):
83+
for name in names:
84+
if cmdline.startswith(name.replace(' ', '')):
85+
return ptype
86+
87+
# default
88+
return ProcessType.Unknown

testgres/node.py

Lines changed: 105 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22

33
import io
44
import os
5-
import six
5+
import psutil
66
import subprocess
77
import time
88

99
from shutil import rmtree
10-
from six import raise_from
10+
from six import raise_from, iteritems
1111
from tempfile import mkstemp, mkdtemp
1212

13-
from .enums import NodeStatus
13+
from .enums import NodeStatus, ProcessType
1414

1515
from .cache import cached_initdb
1616

@@ -49,7 +49,8 @@
4949
QueryException, \
5050
StartNodeException, \
5151
TimeoutException, \
52-
InitNodeException
52+
InitNodeException, \
53+
TestgresException
5354

5455
from .logger import TestgresLogger
5556

@@ -67,6 +68,26 @@
6768
from .backup import NodeBackup
6869

6970

71+
class ProcessProxy(object):
72+
"""
73+
Wrapper for psutil.Process
74+
75+
Attributes:
76+
process: wrapped psutill.Process object
77+
ptype: instance of ProcessType
78+
"""
79+
80+
def __init__(self, process):
81+
self.process = process
82+
self.ptype = ProcessType.from_process(process)
83+
84+
def __getattr__(self, name):
85+
return getattr(self.process, name)
86+
87+
def __repr__(self):
88+
return '{} : {}'.format(str(self.ptype), repr(self.process))
89+
90+
7091
class PostgresNode(object):
7192
def __init__(self, name=None, port=None, base_dir=None):
7293
"""
@@ -119,7 +140,84 @@ def __exit__(self, type, value, traceback):
119140

120141
@property
121142
def pid(self):
122-
return self.get_pid()
143+
"""
144+
Return postmaster's PID if node is running, else 0.
145+
"""
146+
147+
if self.status():
148+
pid_file = os.path.join(self.data_dir, PG_PID_FILE)
149+
with io.open(pid_file) as f:
150+
return int(f.readline())
151+
152+
# for clarity
153+
return 0
154+
155+
@property
156+
def auxiliary_pids(self):
157+
"""
158+
Returns a dict of { ProcessType : PID }.
159+
"""
160+
161+
result = {}
162+
163+
for process in self.auxiliary_processes:
164+
if process.ptype not in result:
165+
result[process.ptype] = []
166+
167+
result[process.ptype].append(process.pid)
168+
169+
return result
170+
171+
@property
172+
def auxiliary_processes(self):
173+
"""
174+
Returns a list of auxiliary processes.
175+
Each process is represented by ProcessProxy object.
176+
"""
177+
178+
def is_aux(process):
179+
return process.ptype != ProcessType.Unknown
180+
181+
return list(filter(is_aux, self.child_processes))
182+
183+
@property
184+
def child_processes(self):
185+
"""
186+
Returns a list of all child processes.
187+
Each process is represented by ProcessProxy object.
188+
"""
189+
190+
# get a list of postmaster's children
191+
children = psutil.Process(self.pid).children()
192+
193+
return [ProcessProxy(p) for p in children]
194+
195+
@property
196+
def source_walsender(self):
197+
"""
198+
Returns master's walsender feeding this replica.
199+
"""
200+
201+
sql = """
202+
select pid
203+
from pg_catalog.pg_stat_replication
204+
where application_name = $1
205+
"""
206+
207+
if not self.master:
208+
raise TestgresException("Node doesn't have a master")
209+
210+
# master should be on the same host
211+
assert self.master.host == self.host
212+
213+
with self.master.connect() as con:
214+
for row in con.execute(sql, self.name):
215+
for child in self.master.auxiliary_processes:
216+
if child.pid == int(row[0]):
217+
return child
218+
219+
msg = "Master doesn't send WAL to {}".format(self.name)
220+
raise TestgresException(msg)
123221

124222
@property
125223
def master(self):
@@ -428,19 +526,6 @@ def status(self):
428526
elif e.exit_code == 4:
429527
return NodeStatus.Uninitialized
430528

431-
def get_pid(self):
432-
"""
433-
Return postmaster's PID if node is running, else 0.
434-
"""
435-
436-
if self.status():
437-
pid_file = os.path.join(self.data_dir, PG_PID_FILE)
438-
with io.open(pid_file) as f:
439-
return int(f.readline())
440-
441-
# for clarity
442-
return 0
443-
444529
def get_control_data(self):
445530
"""
446531
Return contents of pg_control file.
@@ -895,7 +980,7 @@ def catchup(self, dbname=None, username=None):
895980
"""
896981

897982
if not self.master:
898-
raise CatchUpException("Node doesn't have a master")
983+
raise TestgresException("Node doesn't have a master")
899984

900985
if pg_version_ge('10'):
901986
poll_lsn = "select pg_current_wal_lsn()::text"
@@ -1042,7 +1127,7 @@ def pgbench_run(self,
10421127
"-U", username,
10431128
] + options
10441129

1045-
for key, value in six.iteritems(kwargs):
1130+
for key, value in iteritems(kwargs):
10461131
# rename keys for pgbench
10471132
key = key.replace('_', '-')
10481133

tests/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export PG_BIN=/path/to/pg/bin
2121

2222
```bash
2323
# Set path to PostgreSQL and python version
24-
export PATH=$PATH:/path/to/pg/bin
24+
export PATH=/path/to/pg/bin:$PATH
2525
export PYTHON_VERSION=3 # or 2
2626

2727
# Run tests

0 commit comments

Comments
 (0)