testgres 1.11.0__tar.gz → 1.12.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. testgres-1.12.0/PKG-INFO +232 -0
  2. testgres-1.12.0/README.md +204 -0
  3. {testgres-1.11.0 → testgres-1.12.0}/setup.py +5 -3
  4. {testgres-1.11.0/testgres → testgres-1.12.0/src}/__init__.py +8 -6
  5. {testgres-1.11.0/testgres → testgres-1.12.0/src}/backup.py +18 -14
  6. {testgres-1.11.0/testgres → testgres-1.12.0/src}/cache.py +10 -8
  7. {testgres-1.11.0/testgres → testgres-1.12.0/src}/config.py +4 -3
  8. testgres-1.12.0/src/exceptions.py +71 -0
  9. testgres-1.11.0/testgres/port_manager.py → testgres-1.12.0/src/impl/port_manager__generic.py +38 -44
  10. testgres-1.12.0/src/impl/port_manager__this_host.py +33 -0
  11. {testgres-1.11.0/testgres → testgres-1.12.0/src}/node.py +286 -233
  12. testgres-1.12.0/src/node_app.py +317 -0
  13. testgres-1.12.0/src/port_manager.py +10 -0
  14. {testgres-1.11.0/testgres → testgres-1.12.0/src}/utils.py +28 -37
  15. testgres-1.12.0/testgres.egg-info/PKG-INFO +232 -0
  16. testgres-1.12.0/testgres.egg-info/SOURCES.txt +43 -0
  17. {testgres-1.11.0 → testgres-1.12.0}/testgres.egg-info/requires.txt +1 -0
  18. {testgres-1.11.0 → testgres-1.12.0}/tests/conftest.py +308 -142
  19. {testgres-1.11.0 → testgres-1.12.0}/tests/helpers/global_data.py +9 -9
  20. {testgres-1.11.0 → testgres-1.12.0}/tests/test_config.py +5 -5
  21. {testgres-1.11.0 → testgres-1.12.0}/tests/test_os_ops_common.py +327 -2
  22. {testgres-1.11.0 → testgres-1.12.0}/tests/test_os_ops_remote.py +1 -1
  23. {testgres-1.11.0 → testgres-1.12.0}/tests/test_testgres_common.py +484 -31
  24. {testgres-1.11.0 → testgres-1.12.0}/tests/test_testgres_local.py +25 -25
  25. {testgres-1.11.0 → testgres-1.12.0}/tests/test_testgres_remote.py +7 -8
  26. {testgres-1.11.0 → testgres-1.12.0}/tests/test_utils.py +3 -3
  27. testgres-1.11.0/PKG-INFO +0 -236
  28. testgres-1.11.0/README.md +0 -209
  29. testgres-1.11.0/testgres/exceptions.py +0 -113
  30. testgres-1.11.0/testgres/operations/helpers.py +0 -55
  31. testgres-1.11.0/testgres/operations/local_ops.py +0 -502
  32. testgres-1.11.0/testgres/operations/os_ops.py +0 -124
  33. testgres-1.11.0/testgres/operations/raise_error.py +0 -57
  34. testgres-1.11.0/testgres/operations/remote_ops.py +0 -741
  35. testgres-1.11.0/testgres/plugins/__init__.py +0 -8
  36. testgres-1.11.0/testgres/plugins/pg_probackup2/pg_probackup2/__init__.py +0 -0
  37. testgres-1.11.0/testgres/plugins/pg_probackup2/pg_probackup2/app.py +0 -863
  38. testgres-1.11.0/testgres/plugins/pg_probackup2/pg_probackup2/gdb.py +0 -349
  39. testgres-1.11.0/testgres/plugins/pg_probackup2/pg_probackup2/init_helpers.py +0 -226
  40. testgres-1.11.0/testgres/plugins/pg_probackup2/pg_probackup2/storage/__init__.py +0 -0
  41. testgres-1.11.0/testgres/plugins/pg_probackup2/pg_probackup2/storage/fs_backup.py +0 -104
  42. testgres-1.11.0/testgres/plugins/pg_probackup2/pg_probackup2/tests/__init__.py +0 -0
  43. testgres-1.11.0/testgres/plugins/pg_probackup2/pg_probackup2/tests/test_basic.py +0 -95
  44. testgres-1.11.0/testgres/plugins/pg_probackup2/setup.py +0 -18
  45. testgres-1.11.0/testgres.egg-info/PKG-INFO +0 -236
  46. testgres-1.11.0/testgres.egg-info/SOURCES.txt +0 -57
  47. testgres-1.11.0/tests/__init__.py +0 -0
  48. testgres-1.11.0/tests/helpers/__init__.py +0 -0
  49. {testgres-1.11.0 → testgres-1.12.0}/LICENSE +0 -0
  50. {testgres-1.11.0 → testgres-1.12.0}/MANIFEST.in +0 -0
  51. {testgres-1.11.0 → testgres-1.12.0}/setup.cfg +0 -0
  52. {testgres-1.11.0/testgres → testgres-1.12.0/src}/api.py +0 -0
  53. {testgres-1.11.0/testgres → testgres-1.12.0/src}/connection.py +0 -0
  54. {testgres-1.11.0/testgres → testgres-1.12.0/src}/consts.py +0 -0
  55. {testgres-1.11.0/testgres → testgres-1.12.0/src}/decorators.py +0 -0
  56. {testgres-1.11.0/testgres → testgres-1.12.0/src}/defaults.py +0 -0
  57. {testgres-1.11.0/testgres → testgres-1.12.0/src}/enums.py +0 -0
  58. {testgres-1.11.0/testgres → testgres-1.12.0/src}/logger.py +0 -0
  59. {testgres-1.11.0/testgres → testgres-1.12.0/src}/pubsub.py +0 -0
  60. {testgres-1.11.0/testgres → testgres-1.12.0/src}/standby.py +0 -0
  61. {testgres-1.11.0 → testgres-1.12.0}/testgres.egg-info/dependency_links.txt +0 -0
  62. {testgres-1.11.0 → testgres-1.12.0}/testgres.egg-info/top_level.txt +0 -0
  63. {testgres-1.11.0/testgres/operations → testgres-1.12.0/tests}/__init__.py +0 -0
  64. {testgres-1.11.0/testgres/plugins/pg_probackup2 → testgres-1.12.0/tests/helpers}/__init__.py +0 -0
  65. {testgres-1.11.0 → testgres-1.12.0}/tests/helpers/run_conditions.py +0 -0
  66. {testgres-1.11.0 → testgres-1.12.0}/tests/test_os_ops_local.py +0 -0
@@ -0,0 +1,232 @@
1
+ Metadata-Version: 2.4
2
+ Name: testgres
3
+ Version: 1.12.0
4
+ Summary: Testing utility for PostgreSQL and its extensions
5
+ Home-page: https://github.com/postgrespro/testgres
6
+ Author: Postgres Professional
7
+ Author-email: testgres@postgrespro.ru
8
+ License: PostgreSQL
9
+ Keywords: test,testing,postgresql
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: pg8000
13
+ Requires-Dist: port-for>=0.4
14
+ Requires-Dist: six>=1.9.0
15
+ Requires-Dist: psutil
16
+ Requires-Dist: packaging
17
+ Requires-Dist: testgres.os_ops<1.0.0,>=0.0.2
18
+ Dynamic: author
19
+ Dynamic: author-email
20
+ Dynamic: description
21
+ Dynamic: description-content-type
22
+ Dynamic: home-page
23
+ Dynamic: keywords
24
+ Dynamic: license
25
+ Dynamic: license-file
26
+ Dynamic: requires-dist
27
+ Dynamic: summary
28
+
29
+ [![Build Status](https://api.travis-ci.com/postgrespro/testgres.svg?branch=master)](https://travis-ci.com/github/postgrespro/testgres)
30
+ [![codecov](https://codecov.io/gh/postgrespro/testgres/branch/master/graph/badge.svg)](https://codecov.io/gh/postgrespro/testgres)
31
+ [![PyPI version](https://badge.fury.io/py/testgres.svg)](https://badge.fury.io/py/testgres)
32
+
33
+ [Documentation](https://postgrespro.github.io/testgres/)
34
+
35
+ # testgres
36
+
37
+ Utility for orchestrating temporary PostgreSQL clusters in Python tests. Supports Python 3.7.17 and newer.
38
+
39
+ ## Installation
40
+
41
+ Install `testgres` from PyPI:
42
+
43
+ ```sh
44
+ pip install testgres
45
+ ```
46
+
47
+ Use a dedicated virtual environment for isolated test dependencies.
48
+
49
+ ## Usage
50
+
51
+ ### Environment
52
+
53
+ > Note: by default `testgres` invokes `initdb`, `pg_ctl`, and `psql` binaries found in `PATH`.
54
+
55
+ Specify a custom PostgreSQL installation in one of the following ways:
56
+
57
+ - Set the `PG_CONFIG` environment variable to point to the `pg_config` executable.
58
+ - Set the `PG_BIN` environment variable to point to the directory with PostgreSQL binaries.
59
+
60
+ Example:
61
+
62
+ ```sh
63
+ export PG_BIN=$HOME/pg_16/bin
64
+ python my_tests.py
65
+ ```
66
+
67
+ ### Examples
68
+
69
+ Create a temporary node, run queries, and let `testgres` clean up automatically:
70
+
71
+ ```python
72
+ # create a node with a random name, port, and data directory
73
+ with testgres.get_new_node() as node:
74
+
75
+ # run initdb
76
+ node.init()
77
+
78
+ # start PostgreSQL
79
+ node.start()
80
+
81
+ # execute a query in the default database
82
+ print(node.execute('select 1'))
83
+
84
+ # the node is stopped and its files are removed automatically
85
+ ```
86
+
87
+ ### Query helpers
88
+
89
+ `testgres` provides four helpers for executing queries against the node:
90
+
91
+ | Command | Description |
92
+ |---------|-------------|
93
+ | `node.psql(query, ...)` | Runs the query via `psql` and returns a tuple `(returncode, stdout, stderr)`. |
94
+ | `node.safe_psql(query, ...)` | Same as `psql()` but returns only `stdout` and raises if the command fails. |
95
+ | `node.execute(query, ...)` | Connects via `psycopg2` or `pg8000` (whichever is available) and returns a list of tuples. |
96
+ | `node.connect(dbname, ...)` | Returns a `NodeConnection` wrapper for executing multiple statements within a transaction. |
97
+
98
+ Example of transactional usage:
99
+
100
+ ```python
101
+ with node.connect() as con:
102
+ con.begin('serializable')
103
+ print(con.execute('select %s', 1))
104
+ con.rollback()
105
+ ```
106
+
107
+ ### Logging
108
+
109
+ By default `cleanup()` removes all temporary files (data directories, logs, and so on) created by the API. Call `configure_testgres(node_cleanup_full=False)` before starting nodes if you want to keep logs for inspection.
110
+
111
+ > Note: context managers (the `with` statement) call `stop()` and `cleanup()` automatically.
112
+
113
+ `testgres` integrates with the standard [Python logging](https://docs.python.org/3/library/logging.html) module, so you can aggregate logs from multiple nodes:
114
+
115
+ ```python
116
+ import logging
117
+
118
+ # write everything to /tmp/testgres.log
119
+ logging.basicConfig(filename='/tmp/testgres.log')
120
+
121
+ # enable logging and create two nodes
122
+ testgres.configure_testgres(use_python_logging=True)
123
+ node1 = testgres.get_new_node().init().start()
124
+ node2 = testgres.get_new_node().init().start()
125
+
126
+ node1.execute('select 1')
127
+ node2.execute('select 2')
128
+
129
+ # disable logging
130
+ testgres.configure_testgres(use_python_logging=False)
131
+ ```
132
+
133
+ See `tests/test_simple.py` for a complete logging example.
134
+
135
+ ### Backup and replication
136
+
137
+ Creating backups and spawning replicas is straightforward:
138
+
139
+ ```python
140
+ with testgres.get_new_node('master') as master:
141
+ master.init().start()
142
+
143
+ with master.backup() as backup:
144
+ replica = backup.spawn_replica('replica').start()
145
+ replica.catchup()
146
+
147
+ print(replica.execute('postgres', 'select 1'))
148
+ ```
149
+
150
+ ### Benchmarks
151
+
152
+ Use `pgbench` through `testgres` to run quick benchmarks:
153
+
154
+ ```python
155
+ with testgres.get_new_node('master') as master:
156
+ master.init().start()
157
+
158
+ result = master.pgbench_init(scale=2).pgbench_run(time=10)
159
+ print(result)
160
+ ```
161
+
162
+ ### Custom configuration
163
+
164
+ `testgres` ships with sensible defaults. Adjust them as needed with `default_conf()` and `append_conf()`:
165
+
166
+ ```python
167
+ extra_conf = "shared_preload_libraries = 'postgres_fdw'"
168
+
169
+ with testgres.get_new_node().init() as master:
170
+ master.default_conf(fsync=True, allow_streaming=True)
171
+ master.append_conf('postgresql.conf', extra_conf)
172
+ ```
173
+
174
+ `default_conf()` is called by `init()` and rewrites the configuration file. Apply `append_conf()` afterwards to keep custom lines.
175
+
176
+ ### Remote mode
177
+
178
+ You can provision nodes on a remote host (Linux only) by wiring `RemoteOperations` into the configuration:
179
+
180
+ ```python
181
+ from testgres import ConnectionParams, RemoteOperations, TestgresConfig, get_remote_node
182
+
183
+ conn_params = ConnectionParams(
184
+ host='example.com',
185
+ username='postgres',
186
+ ssh_key='/path/to/ssh/key'
187
+ )
188
+ os_ops = RemoteOperations(conn_params)
189
+
190
+ TestgresConfig.set_os_ops(os_ops=os_ops)
191
+
192
+ def test_basic_query():
193
+ with get_remote_node(conn_params=conn_params) as node:
194
+ node.init().start()
195
+ assert node.execute('SELECT 1') == [(1,)]
196
+ ```
197
+
198
+ ### Pytest integration
199
+
200
+ Use fixtures to create and clean up nodes automatically when testing with `pytest`:
201
+
202
+ ```python
203
+ import pytest
204
+ import testgres
205
+
206
+ @pytest.fixture
207
+ def pg_node():
208
+ node = testgres.get_new_node().init().start()
209
+ try:
210
+ yield node
211
+ finally:
212
+ node.stop()
213
+ node.cleanup()
214
+
215
+ def test_simple(pg_node):
216
+ assert pg_node.execute('select 1')[0][0] == 1
217
+ ```
218
+
219
+ This pattern keeps tests concise and ensures that every node is stopped and removed even if the test fails.
220
+
221
+ ### Scaling tips
222
+
223
+ - Run tests in parallel with `pytest -n auto` (requires `pytest-xdist`). Ensure each node uses a distinct port by setting `PGPORT` in the fixture or by passing the `port` argument to `get_new_node()`.
224
+ - Always call `node.cleanup()` after each test, or rely on context managers/fixtures that do it for you, to avoid leftover data directories.
225
+ - Prefer `node.safe_psql()` for lightweight assertions that should fail fast; use `node.execute()` when you need structured Python results.
226
+
227
+ ## Authors
228
+
229
+ [Ildar Musin](https://github.com/zilder)
230
+ [Dmitry Ivanov](https://github.com/funbringer)
231
+ [Ildus Kurbangaliev](https://github.com/ildus)
232
+ [Yury Zhuravlev](https://github.com/stalkerg)
@@ -0,0 +1,204 @@
1
+ [![Build Status](https://api.travis-ci.com/postgrespro/testgres.svg?branch=master)](https://travis-ci.com/github/postgrespro/testgres)
2
+ [![codecov](https://codecov.io/gh/postgrespro/testgres/branch/master/graph/badge.svg)](https://codecov.io/gh/postgrespro/testgres)
3
+ [![PyPI version](https://badge.fury.io/py/testgres.svg)](https://badge.fury.io/py/testgres)
4
+
5
+ [Documentation](https://postgrespro.github.io/testgres/)
6
+
7
+ # testgres
8
+
9
+ Utility for orchestrating temporary PostgreSQL clusters in Python tests. Supports Python 3.7.17 and newer.
10
+
11
+ ## Installation
12
+
13
+ Install `testgres` from PyPI:
14
+
15
+ ```sh
16
+ pip install testgres
17
+ ```
18
+
19
+ Use a dedicated virtual environment for isolated test dependencies.
20
+
21
+ ## Usage
22
+
23
+ ### Environment
24
+
25
+ > Note: by default `testgres` invokes `initdb`, `pg_ctl`, and `psql` binaries found in `PATH`.
26
+
27
+ Specify a custom PostgreSQL installation in one of the following ways:
28
+
29
+ - Set the `PG_CONFIG` environment variable to point to the `pg_config` executable.
30
+ - Set the `PG_BIN` environment variable to point to the directory with PostgreSQL binaries.
31
+
32
+ Example:
33
+
34
+ ```sh
35
+ export PG_BIN=$HOME/pg_16/bin
36
+ python my_tests.py
37
+ ```
38
+
39
+ ### Examples
40
+
41
+ Create a temporary node, run queries, and let `testgres` clean up automatically:
42
+
43
+ ```python
44
+ # create a node with a random name, port, and data directory
45
+ with testgres.get_new_node() as node:
46
+
47
+ # run initdb
48
+ node.init()
49
+
50
+ # start PostgreSQL
51
+ node.start()
52
+
53
+ # execute a query in the default database
54
+ print(node.execute('select 1'))
55
+
56
+ # the node is stopped and its files are removed automatically
57
+ ```
58
+
59
+ ### Query helpers
60
+
61
+ `testgres` provides four helpers for executing queries against the node:
62
+
63
+ | Command | Description |
64
+ |---------|-------------|
65
+ | `node.psql(query, ...)` | Runs the query via `psql` and returns a tuple `(returncode, stdout, stderr)`. |
66
+ | `node.safe_psql(query, ...)` | Same as `psql()` but returns only `stdout` and raises if the command fails. |
67
+ | `node.execute(query, ...)` | Connects via `psycopg2` or `pg8000` (whichever is available) and returns a list of tuples. |
68
+ | `node.connect(dbname, ...)` | Returns a `NodeConnection` wrapper for executing multiple statements within a transaction. |
69
+
70
+ Example of transactional usage:
71
+
72
+ ```python
73
+ with node.connect() as con:
74
+ con.begin('serializable')
75
+ print(con.execute('select %s', 1))
76
+ con.rollback()
77
+ ```
78
+
79
+ ### Logging
80
+
81
+ By default `cleanup()` removes all temporary files (data directories, logs, and so on) created by the API. Call `configure_testgres(node_cleanup_full=False)` before starting nodes if you want to keep logs for inspection.
82
+
83
+ > Note: context managers (the `with` statement) call `stop()` and `cleanup()` automatically.
84
+
85
+ `testgres` integrates with the standard [Python logging](https://docs.python.org/3/library/logging.html) module, so you can aggregate logs from multiple nodes:
86
+
87
+ ```python
88
+ import logging
89
+
90
+ # write everything to /tmp/testgres.log
91
+ logging.basicConfig(filename='/tmp/testgres.log')
92
+
93
+ # enable logging and create two nodes
94
+ testgres.configure_testgres(use_python_logging=True)
95
+ node1 = testgres.get_new_node().init().start()
96
+ node2 = testgres.get_new_node().init().start()
97
+
98
+ node1.execute('select 1')
99
+ node2.execute('select 2')
100
+
101
+ # disable logging
102
+ testgres.configure_testgres(use_python_logging=False)
103
+ ```
104
+
105
+ See `tests/test_simple.py` for a complete logging example.
106
+
107
+ ### Backup and replication
108
+
109
+ Creating backups and spawning replicas is straightforward:
110
+
111
+ ```python
112
+ with testgres.get_new_node('master') as master:
113
+ master.init().start()
114
+
115
+ with master.backup() as backup:
116
+ replica = backup.spawn_replica('replica').start()
117
+ replica.catchup()
118
+
119
+ print(replica.execute('postgres', 'select 1'))
120
+ ```
121
+
122
+ ### Benchmarks
123
+
124
+ Use `pgbench` through `testgres` to run quick benchmarks:
125
+
126
+ ```python
127
+ with testgres.get_new_node('master') as master:
128
+ master.init().start()
129
+
130
+ result = master.pgbench_init(scale=2).pgbench_run(time=10)
131
+ print(result)
132
+ ```
133
+
134
+ ### Custom configuration
135
+
136
+ `testgres` ships with sensible defaults. Adjust them as needed with `default_conf()` and `append_conf()`:
137
+
138
+ ```python
139
+ extra_conf = "shared_preload_libraries = 'postgres_fdw'"
140
+
141
+ with testgres.get_new_node().init() as master:
142
+ master.default_conf(fsync=True, allow_streaming=True)
143
+ master.append_conf('postgresql.conf', extra_conf)
144
+ ```
145
+
146
+ `default_conf()` is called by `init()` and rewrites the configuration file. Apply `append_conf()` afterwards to keep custom lines.
147
+
148
+ ### Remote mode
149
+
150
+ You can provision nodes on a remote host (Linux only) by wiring `RemoteOperations` into the configuration:
151
+
152
+ ```python
153
+ from testgres import ConnectionParams, RemoteOperations, TestgresConfig, get_remote_node
154
+
155
+ conn_params = ConnectionParams(
156
+ host='example.com',
157
+ username='postgres',
158
+ ssh_key='/path/to/ssh/key'
159
+ )
160
+ os_ops = RemoteOperations(conn_params)
161
+
162
+ TestgresConfig.set_os_ops(os_ops=os_ops)
163
+
164
+ def test_basic_query():
165
+ with get_remote_node(conn_params=conn_params) as node:
166
+ node.init().start()
167
+ assert node.execute('SELECT 1') == [(1,)]
168
+ ```
169
+
170
+ ### Pytest integration
171
+
172
+ Use fixtures to create and clean up nodes automatically when testing with `pytest`:
173
+
174
+ ```python
175
+ import pytest
176
+ import testgres
177
+
178
+ @pytest.fixture
179
+ def pg_node():
180
+ node = testgres.get_new_node().init().start()
181
+ try:
182
+ yield node
183
+ finally:
184
+ node.stop()
185
+ node.cleanup()
186
+
187
+ def test_simple(pg_node):
188
+ assert pg_node.execute('select 1')[0][0] == 1
189
+ ```
190
+
191
+ This pattern keeps tests concise and ensures that every node is stopped and removed even if the test fails.
192
+
193
+ ### Scaling tips
194
+
195
+ - Run tests in parallel with `pytest -n auto` (requires `pytest-xdist`). Ensure each node uses a distinct port by setting `PGPORT` in the fixture or by passing the `port` argument to `get_new_node()`.
196
+ - Always call `node.cleanup()` after each test, or rely on context managers/fixtures that do it for you, to avoid leftover data directories.
197
+ - Prefer `node.safe_psql()` for lightweight assertions that should fail fast; use `node.execute()` when you need structured Python results.
198
+
199
+ ## Authors
200
+
201
+ [Ildar Musin](https://github.com/zilder)
202
+ [Dmitry Ivanov](https://github.com/funbringer)
203
+ [Ildus Kurbangaliev](https://github.com/ildus)
204
+ [Yury Zhuravlev](https://github.com/stalkerg)
@@ -11,7 +11,8 @@ install_requires = [
11
11
  "port-for>=0.4",
12
12
  "six>=1.9.0",
13
13
  "psutil",
14
- "packaging"
14
+ "packaging",
15
+ "testgres.os_ops>=0.0.2,<1.0.0"
15
16
  ]
16
17
 
17
18
  # Add compatibility enum class
@@ -27,9 +28,10 @@ with open('README.md', 'r') as f:
27
28
  readme = f.read()
28
29
 
29
30
  setup(
30
- version='1.11.0',
31
+ version='1.12.0',
31
32
  name='testgres',
32
- packages=['testgres', 'testgres.operations'],
33
+ packages=['testgres', 'testgres.impl'],
34
+ package_dir={"testgres": "src"},
33
35
  description='Testing utility for PostgreSQL and its extensions',
34
36
  url='https://github.com/postgrespro/testgres',
35
37
  long_description=readme,
@@ -33,8 +33,9 @@ from .enums import \
33
33
  ProcessType, \
34
34
  DumpFormat
35
35
 
36
- from .node import PostgresNode, NodeApp
36
+ from .node import PostgresNode
37
37
  from .node import PortManager
38
+ from .node_app import NodeApp
38
39
 
39
40
  from .utils import \
40
41
  reserve_port, \
@@ -50,9 +51,9 @@ from .standby import \
50
51
 
51
52
  from .config import testgres_config
52
53
 
53
- from .operations.os_ops import OsOperations, ConnectionParams
54
- from .operations.local_ops import LocalOperations
55
- from .operations.remote_ops import RemoteOperations
54
+ from testgres.operations.os_ops import OsOperations, ConnectionParams
55
+ from testgres.operations.local_ops import LocalOperations
56
+ from testgres.operations.remote_ops import RemoteOperations
56
57
 
57
58
  __all__ = [
58
59
  "get_new_node",
@@ -62,8 +63,9 @@ __all__ = [
62
63
  "NodeConnection", "DatabaseError", "InternalError", "ProgrammingError", "OperationalError",
63
64
  "TestgresException", "ExecUtilException", "QueryException", "TimeoutException", "CatchUpException", "StartNodeException", "InitNodeException", "BackupException", "InvalidOperationException",
64
65
  "XLogMethod", "IsolationLevel", "NodeStatus", "ProcessType", "DumpFormat",
65
- "PostgresNode", "NodeApp",
66
- "PortManager",
66
+ NodeApp.__name__,
67
+ PostgresNode.__name__,
68
+ PortManager.__name__,
67
69
  "reserve_port", "release_port", "bound_ports", "get_bin_path", "get_pg_config", "get_pg_version",
68
70
  "First", "Any",
69
71
  "OsOperations", "LocalOperations", "RemoteOperations", "ConnectionParams"
@@ -1,7 +1,5 @@
1
1
  # coding: utf-8
2
2
 
3
- import os
4
-
5
3
  from six import raise_from
6
4
 
7
5
  from .enums import XLogMethod
@@ -15,7 +13,7 @@ from .consts import \
15
13
 
16
14
  from .exceptions import BackupException
17
15
 
18
- from .operations.os_ops import OsOperations
16
+ from testgres.operations.os_ops import OsOperations
19
17
 
20
18
  from .utils import \
21
19
  get_bin_path2, \
@@ -29,7 +27,9 @@ class NodeBackup(object):
29
27
  """
30
28
  @property
31
29
  def log_file(self):
32
- return os.path.join(self.base_dir, BACKUP_LOG_FILE)
30
+ assert self.os_ops is not None
31
+ assert isinstance(self.os_ops, OsOperations)
32
+ return self.os_ops.build_path(self.base_dir, BACKUP_LOG_FILE)
33
33
 
34
34
  def __init__(self,
35
35
  node,
@@ -75,7 +75,7 @@ class NodeBackup(object):
75
75
  # private
76
76
  self._available = True
77
77
 
78
- data_dir = os.path.join(self.base_dir, DATA_DIR)
78
+ data_dir = self.os_ops.build_path(self.base_dir, DATA_DIR)
79
79
 
80
80
  _params = [
81
81
  get_bin_path2(self.os_ops, "pg_basebackup"),
@@ -112,10 +112,13 @@ class NodeBackup(object):
112
112
  available = not destroy
113
113
 
114
114
  if available:
115
+ assert self.os_ops is not None
116
+ assert isinstance(self.os_ops, OsOperations)
117
+
115
118
  dest_base_dir = self.os_ops.mkdtemp(prefix=TMP_NODE)
116
119
 
117
- data1 = os.path.join(self.base_dir, DATA_DIR)
118
- data2 = os.path.join(dest_base_dir, DATA_DIR)
120
+ data1 = self.os_ops.build_path(self.base_dir, DATA_DIR)
121
+ data2 = self.os_ops.build_path(dest_base_dir, DATA_DIR)
119
122
 
120
123
  try:
121
124
  # Copy backup to new data dir
@@ -160,10 +163,6 @@ class NodeBackup(object):
160
163
  assert type(node) == self.original_node.__class__ # noqa: E721
161
164
 
162
165
  with clean_on_error(node) as node:
163
-
164
- # New nodes should always remove dir tree
165
- node._should_rm_dirs = True
166
-
167
166
  # Set a new port
168
167
  node.append_conf(filename=PG_CONF_FILE, line='\n')
169
168
  node.append_conf(filename=PG_CONF_FILE, port=node.port)
@@ -184,14 +183,19 @@ class NodeBackup(object):
184
183
  """
185
184
 
186
185
  # Build a new PostgresNode
187
- with clean_on_error(self.spawn_primary(name=name,
188
- destroy=destroy)) as node:
186
+ node = self.spawn_primary(name=name, destroy=destroy)
187
+ assert node is not None
189
188
 
189
+ try:
190
190
  # Assign it a master and a recovery file (private magic)
191
191
  node._assign_master(self.original_node)
192
192
  node._create_recovery_conf(username=self.username, slot=slot)
193
+ except: # noqa: E722
194
+ # TODO: Pass 'final=True' ?
195
+ node.cleanup(release_resources=True)
196
+ raise
193
197
 
194
- return node
198
+ return node
195
199
 
196
200
  def cleanup(self):
197
201
  """
@@ -1,7 +1,5 @@
1
1
  # coding: utf-8
2
2
 
3
- import os
4
-
5
3
  from six import raise_from
6
4
 
7
5
  from .config import testgres_config
@@ -18,16 +16,20 @@ from .utils import \
18
16
  get_bin_path2, \
19
17
  execute_utility2
20
18
 
21
- from .operations.local_ops import LocalOperations
22
- from .operations.os_ops import OsOperations
19
+ from testgres.operations.local_ops import LocalOperations
20
+ from testgres.operations.os_ops import OsOperations
23
21
 
24
22
 
25
- def cached_initdb(data_dir, logfile=None, params=None, os_ops: OsOperations = LocalOperations(), bin_path=None, cached=True):
23
+ def cached_initdb(data_dir, logfile=None, params=None, os_ops: OsOperations = None, bin_path=None, cached=True):
26
24
  """
27
25
  Perform initdb or use cached node files.
28
26
  """
29
27
 
30
- assert os_ops is not None
28
+ assert os_ops is None or isinstance(os_ops, OsOperations)
29
+
30
+ if os_ops is None:
31
+ os_ops = LocalOperations.get_single_instance()
32
+
31
33
  assert isinstance(os_ops, OsOperations)
32
34
 
33
35
  def make_utility_path(name):
@@ -35,7 +37,7 @@ def cached_initdb(data_dir, logfile=None, params=None, os_ops: OsOperations = Lo
35
37
  assert type(name) == str # noqa: E721
36
38
 
37
39
  if bin_path:
38
- return os.path.join(bin_path, name)
40
+ return os_ops.build_path(bin_path, name)
39
41
 
40
42
  return get_bin_path2(os_ops, name)
41
43
 
@@ -68,7 +70,7 @@ def cached_initdb(data_dir, logfile=None, params=None, os_ops: OsOperations = Lo
68
70
  # XXX: write new unique system id to control file
69
71
  # Some users might rely upon unique system ids, but
70
72
  # our initdb caching mechanism breaks this contract.
71
- pg_control = os.path.join(data_dir, XLOG_CONTROL_FILE)
73
+ pg_control = os_ops.build_path(data_dir, XLOG_CONTROL_FILE)
72
74
  system_id = generate_system_id()
73
75
  cur_pg_control = os_ops.read(pg_control, binary=True)
74
76
  new_pg_control = system_id + cur_pg_control[len(system_id):]
@@ -9,8 +9,8 @@ import tempfile
9
9
  from contextlib import contextmanager
10
10
 
11
11
  from .consts import TMP_CACHE
12
- from .operations.os_ops import OsOperations
13
- from .operations.local_ops import LocalOperations
12
+ from testgres.operations.os_ops import OsOperations
13
+ from testgres.operations.local_ops import LocalOperations
14
14
 
15
15
  log_level = os.getenv('LOGGING_LEVEL', 'WARNING').upper()
16
16
  log_format = os.getenv('LOGGING_FORMAT', '%(asctime)s - %(levelname)s - %(message)s')
@@ -50,8 +50,9 @@ class GlobalConfig(object):
50
50
  _cached_initdb_dir = None
51
51
  """ underlying class attribute for cached_initdb_dir property """
52
52
 
53
- os_ops = LocalOperations()
53
+ os_ops = LocalOperations.get_single_instance()
54
54
  """ OsOperation object that allows work on remote host """
55
+
55
56
  @property
56
57
  def cached_initdb_dir(self):
57
58
  """ path to a temp directory for cached initdb. """