Giter Club home page Giter Club logo

testgres's Introduction

Build Status codecov PyPI version

Documentation

testgres

PostgreSQL testing utility. Both Python 2.7 and 3.3+ are supported.

Installation

To install testgres, run:

pip install testgres

We encourage you to use virtualenv for your testing environment.

Usage

Environment

Note: by default testgres runs initdb, pg_ctl, psql provided by PATH.

There are several ways to specify a custom postgres installation:

  • export PG_CONFIG environment variable pointing to the pg_config executable;
  • export PG_BIN environment variable pointing to the directory with executable files.

Example:

export PG_BIN=$HOME/pg_10/bin
python my_tests.py

Examples

Here is an example of what you can do with testgres:

# create a node with random name, port, etc
with testgres.get_new_node() as node:

    # run inidb
    node.init()

    # start PostgreSQL
    node.start()

    # execute a query in a default DB
    print(node.execute('select 1'))

# ... node stops and its files are about to be removed

There are four API methods for runnig queries:

Command Description
node.psql(query, ...) Runs query via psql command and returns tuple (error code, stdout, stderr).
node.safe_psql(query, ...) Same as psql() except that it returns only stdout. If an error occures during the execution, an exception will be thrown.
node.execute(query, ...) Connects to PostgreSQL using psycopg2 or pg8000 (depends on which one is installed in your system) and returns two-dimensional array with data.
node.connect(dbname, ...) Returns connection wrapper (NodeConnection) capable of running several queries within a single transaction.

The last one is the most powerful: you can use begin(isolation_level), commit() and rollback():

with node.connect() as con:
    con.begin('serializable')
    print(con.execute('select %s', 1))
    con.rollback()

Logging

By default, cleanup() removes all temporary files (DB files, logs etc) that were created by testgres' API methods. If you'd like to keep logs, execute configure_testgres(node_cleanup_full=False) before running any tests.

Note: context managers (aka with) call stop() and cleanup() automatically.

testgres supports python logging, which means that you can aggregate logs from several nodes into one file:

import logging

# write everything to /tmp/testgres.log
logging.basicConfig(filename='/tmp/testgres.log')

# enable logging, and create two different nodes
testgres.configure_testgres(use_python_logging=True)
node1 = testgres.get_new_node().init().start()
node2 = testgres.get_new_node().init().start()

# execute a few queries
node1.execute('select 1')
node2.execute('select 2')

# disable logging
testgres.configure_testgres(use_python_logging=False)

Look at tests/test_simple.py file for a complete example of the logging configuration.

Backup & replication

It's quite easy to create a backup and start a new replica:

with testgres.get_new_node('master') as master:
    master.init().start()

    # create a backup
    with master.backup() as backup:

        # create and start a new replica
        replica = backup.spawn_replica('replica').start()

        # catch up with master node
        replica.catchup()

        # execute a dummy query
        print(replica.execute('postgres', 'select 1'))

Benchmarks

testgres is also capable of running benchmarks using pgbench:

with testgres.get_new_node('master') as master:
    # start a new node
    master.init().start()

    # initialize default DB and run bench for 10 seconds
    res = master.pgbench_init(scale=2).pgbench_run(time=10)
    print(res)

Custom configuration

It's often useful to extend default configuration provided by testgres.

testgres has default_conf() function that helps control some basic options. The append_conf() function can be used to add custom lines to configuration lines:

ext_conf = "shared_preload_libraries = 'postgres_fdw'"

# initialize a new node
with testgres.get_new_node().init() as master:

    # ... do something ...
	
    # reset main config file
    master.default_conf(fsync=True,
                        allow_streaming=True)

    # add a new config line
    master.append_conf('postgresql.conf', ext_conf)

Note that default_conf() is called by init() function; both of them overwrite the configuration file, which means that they should be called before append_conf().

Remote mode

Testgres supports the creation of PostgreSQL nodes on a remote host. This is useful when you want to run distributed tests involving multiple nodes spread across different machines.

To use this feature, you need to use the RemoteOperations class. This feature is only supported with Linux. Here is an example of how you might set this up:

from testgres import ConnectionParams, RemoteOperations, TestgresConfig, get_remote_node

# Set up connection params
conn_params = ConnectionParams(
    host='your_host',  # replace with your host
    username='user_name',  # replace with your username
    ssh_key='path_to_ssh_key'  # replace with your SSH key path
)
os_ops = RemoteOperations(conn_params)

# Add remote testgres config before test
TestgresConfig.set_os_ops(os_ops=os_ops)

# Proceed with your test
def test_basic_query(self):
    with get_remote_node(conn_params=conn_params) as node:
        node.init().start()
        res = node.execute('SELECT 1')
        self.assertEqual(res, [(1,)])

Authors

Ildar Musin
Dmitry Ivanov
Ildus Kurbangaliev
Yury Zhuravlev

testgres's People

Contributors

alubennikova avatar andrewbille avatar anisimov-ds avatar arssher avatar asavchkov avatar demonolock avatar dura0ok avatar egarbuz avatar fabriziomello avatar fenimorkin avatar funbringer avatar funny-falcon avatar gsmolk avatar homper avatar ildus avatar komii avatar kulaginm avatar ligurio avatar maksl avatar maksm90 avatar metaldream666 avatar ogurezzz avatar postgres-dev avatar shenghaoyang avatar stalkerg avatar vadv avatar valeria1235 avatar z-kasymalieva avatar zilder avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

testgres's Issues

Version detection fails on Ubuntu (Mint) installation

On Linux Mint (an Ubuntu derivative), the version string produced by postgres isn't parsed correctly by get_pg_version() in utils.py:

$ /usr/lib/postgresql/15/bin/postgres --version
postgres (PostgreSQL) 15.5 (Ubuntu 15.5-1.pgdg22.04+1)

$ python ./tests/test_simple.py 
Traceback (most recent call last):
  File "testgres/env/lib/python3.10/site-packages/testgres/utils.py", line 31, in __init__
    super().__init__(version)
  File "testgres/env/lib/python3.10/site-packages/packaging/version.py", line 200, in __init__
    raise InvalidVersion(f"Invalid version: '{version}'")
packaging.version.InvalidVersion: Invalid version: '15.5-1.pgdg22.04+1)'

There appears to be a fairly simple fix in get_pg_version(), line 185:

    # Remove "(Homebrew)" and "(Ubuntu <version>)" if present
    raw_ver = raw_ver.replace('(Homebrew)', '').strip()
    raw_ver = re.sub('\(Ubuntu[^)]*\)', '', raw_ver).strip()

Node-depend binaries and pg_upgrade support

For example:
node_old = get_new_node(prefix='/path/to/old/pg/prefix')
node_old.init()
node_new=get_new_node(prefix='/path/to/new/pg/prefix')
node_new.init()
node_new.upgade_from(old=node_old)

TypeError: Can't convert 'bytes' object to str implicitly

I get this error if safe_psql() fails:

----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/artur/source/pg/rum/tests/pglist_tests.py", line 94, in test_order_by
    "  FROM pglist "
  File "/home/artur/source/pg/rum/tests/pglist_tests.py", line 83, in test_order_by
    try:
  File "/home/artur/source/pg/rum/tests/rum/lib/python3.5/site-packages/testgres/testgres.py", line 379, in safe_psql
    raise ClusterException("psql failed:\n" + err)
TypeError: Can't convert 'bytes' object to str implicitly

----------------------------------------------------------------------

Testing a DB installed on remote host

Hi,

This is not an issue but just a quick question about the tool. Can I use testgres to test a DB cluster installed on remote host ?

Thanks,
Kapil.

PG13 support

TODO:

  • wal_keep_segments parameter is obsolete

PostgresNode.safe_psql(filename) does not detect syntax errors

It took me some time to discover that a syntax error was being not being reported by node.safe_psql(filename='some.sql'). I tracked it down to behavior in psql. It seems worth raising, though I'm not certain it is something that testgres is necessarily responsible for handling.


$ psql --version
psql (PostgreSQL) 9.6.4

and/or

$ psql --version
psql (PostgreSQL) 10.4

psql reports errors differently depending on how input is passed:

  1. $ psql -h 127.0.0.1 -U cbandy -X -A -t -q -c 'whoops' -p 34751 postgres ; echo $?
    ERROR:  syntax error at or near "whoops"
    LINE 1: whoops
            ^
    1
    
  2. N.B. exit code is zero here:

    $ psql -h 127.0.0.1 -U cbandy -X -A -t -q -f <( echo 'whoops' ) -p 34751 postgres ; echo $?
    psql:/dev/fd/63:1: ERROR:  syntax error at or near "whoops"
    LINE 1: whoops
            ^
    0
    
  3. $ psql -h 127.0.0.1 -U cbandy -X -A -t -q -v ON_ERROR_STOP=1 -f <( echo 'whoops' ) -p 34751 
    postgres ; echo $?
    psql:/dev/fd/63:1: ERROR:  syntax error at or near "whoops"
    LINE 1: whoops
            ^
    3
    

cleanup backward compatibility broken in 1.8.3

In 1.8.2 cleanup() method always removed data directory, 1.8.3 removes data directory only in case of custom base path:

-        # choose directory to be removed
-        if testgres_config.node_cleanup_full:
-            rm_dir = self.base_dir    # everything
-        else:
-            rm_dir = self.data_dir    # just data, save logs
+        # only remove if base directory was temporary
+        if not self._custom_base_dir:
+            # choose directory to be removed
+            if testgres_config.node_cleanup_full:
+                rm_dir = self.base_dir    # everything
+            else:
+                rm_dir = self.data_dir    # just data, save logs
 
-        rmtree(rm_dir, ignore_errors=True)
+            rmtree(rm_dir, ignore_errors=True)

'TypeError: decode() argument 'encoding' when node.init()

Hi,
When I try to create a node on port 5435 and execute init(), I get an error 'TypeError: decode() argument 'encoding' must be str, not None'.
Maybe I'm missing some parameter? How can I troubleshoot this error?

python version: 3.11.4
testgres version: 1.9.2

Here is my code.

import testgres
with testgres.get_new_node('test', port = 5435) as node:
    print(node)

    node.init()
    node.start()

    print(node.execute('select 1'))

    node.stop()
    node.clean()

Error message:
image

[update]
I can excute init() with testgres version 1.8.9.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.