diff options
| -rw-r--r-- | LICENSE | 202 | ||||
| -rw-r--r-- | MANIFEST.in | 5 | ||||
| -rw-r--r-- | PKG-INFO | 229 | ||||
| -rw-r--r-- | README.rst | 206 | ||||
| -rw-r--r-- | debian/.git-dpm | 11 | ||||
| -rw-r--r-- | debian/changelog | 5 | ||||
| -rw-r--r-- | debian/compat | 1 | ||||
| -rw-r--r-- | debian/control | 61 | ||||
| -rw-r--r-- | debian/copyright | 28 | ||||
| -rw-r--r-- | debian/patches/0001-From-Takeshi-KOMIYA-i.tkomiya-gmail.com.patch | 129 | ||||
| -rw-r--r-- | debian/patches/series | 1 | ||||
| -rwxr-xr-x | debian/rules | 12 | ||||
| -rw-r--r-- | debian/source/format | 1 | ||||
| -rw-r--r-- | debian/source/lintian-overrides | 2 | ||||
| -rw-r--r-- | debian/watch | 4 | ||||
| -rw-r--r-- | setup.cfg | 25 | ||||
| -rw-r--r-- | setup.py | 52 | ||||
| -rw-r--r-- | src/testing.postgresql.egg-info/PKG-INFO | 229 | ||||
| -rw-r--r-- | src/testing.postgresql.egg-info/SOURCES.txt | 14 | ||||
| -rw-r--r-- | src/testing.postgresql.egg-info/dependency_links.txt | 1 | ||||
| -rw-r--r-- | src/testing.postgresql.egg-info/namespace_packages.txt | 1 | ||||
| -rw-r--r-- | src/testing.postgresql.egg-info/requires.txt | 7 | ||||
| -rw-r--r-- | src/testing.postgresql.egg-info/top_level.txt | 1 | ||||
| -rw-r--r-- | src/testing/__init__.py | 16 | ||||
| -rw-r--r-- | src/testing/postgresql.py | 144 | ||||
| -rw-r--r-- | tests/test_postgresql.py | 282 |
26 files changed, 1669 insertions, 0 deletions
@@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..e84d857 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,5 @@ +include MANIFEST.in +include LICENSE +include README.rst +recursive-include src *.py +recursive-include tests *.py diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..45dcb11 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,229 @@ +Metadata-Version: 1.1 +Name: testing.postgresql +Version: 1.3.0 +Summary: automatically setups a postgresql instance in a temporary directory, and destroys it after testing +Home-page: https://github.com/tk0miya/testing.postgresql +Author: Takeshi Komiya +Author-email: i.tkomiya at gmail.com +License: Apache License 2.0 +Description: About + ===== + ``testing.postgresql`` automatically setups a postgresql instance in a temporary directory, and destroys it after testing. + + .. image:: https://travis-ci.org/tk0miya/testing.postgresql.svg?branch=master + :target: https://travis-ci.org/tk0miya/testing.postgresql + + .. image:: https://coveralls.io/repos/tk0miya/testing.postgresql/badge.png?branch=master + :target: https://coveralls.io/r/tk0miya/testing.postgresql?branch=master + + .. image:: https://codeclimate.com/github/tk0miya/testing.postgresql/badges/gpa.svg + :target: https://codeclimate.com/github/tk0miya/testing.postgresql + + + Documentation + https://github.com/tk0miya/testing.postgresql + Issues + https://github.com/tk0miya/testing.postgresql/issues + Download + https://pypi.python.org/pypi/testing.postgresql + + Install + ======= + Use pip:: + + $ pip install testing.postgresql + + And ``testing.postgresql`` requires PostgreSQL server in your PATH. + + + Usage + ===== + Create PostgreSQL instance using ``testing.postgresql.Postgresql``:: + + import testing.postgresql + from sqlalchemy import create_engine + + # Lanuch new PostgreSQL server + with testing.postgresql.Postgresql() as postgresql: + # connect to PostgreSQL + engine = create_engine(postgresql.url()) + + # if you use postgresql or other drivers: + # import psycopg2 + # db = psycopg2.connect(**postgresql.dsn()) + + # + # do any tests using PostgreSQL... + # + + # PostgreSQL server is terminated here + + + ``testing.postgresql.Postgresql`` executes ``initdb`` and ``postgres`` on instantiation. + On deleting Postgresql object, it terminates PostgreSQL instance and removes temporary directory. + + If you want a database including tables and any fixtures for your apps, + use ``copy_data_from`` keyword:: + + # uses a copy of specified data directory of PostgreSQL. + postgresql = testing.postgresql.Postgresql(copy_data_from='/path/to/your/database') + + + For example, you can setup new PostgreSQL server for each testcases on setUp() method:: + + import unittest + import testing.postgresql + + class MyTestCase(unittest.TestCase): + def setUp(self): + self.postgresql = testing.postgresql.Postgresql() + + def tearDown(self): + self.postgresql.stop() + + + To make your tests faster + ------------------------- + + ``testing.postgresql.Postgresql`` invokes ``initdb`` command on every instantiation. + That is very simple. But, in many cases, it is very waste that generating brandnew database for each testcase. + + To optimize the behavior, use ``testing.postgresql.PostgresqlFactory``. + The factory class is able to cache the generated database beyond the testcases, + and it reduces the number of invocation of ``initdb`` command:: + + import unittest + import testing.postgresql + + # Generate Postgresql class which shares the generated database + Postgresql = testing.postgresql.PostgresqlFactory(cache_initialized_db=True) + + + def tearDownModule(self): + # clear cached database at end of tests + Postgresql.clear_cache() + + + class MyTestCase(unittest.TestCase): + def setUp(self): + # Use the generated Postgresql class instead of testing.postgresql.Postgresql + self.postgresql = Postgresql() + + def tearDown(self): + self.postgresql.stop() + + If you want to insert fixtures to the cached database, use ``initdb_handler`` option:: + + # create initial data on create as fixtures into the database + def handler(postgresql): + conn = psycopg2.connect(**postgresql.dsn()) + cursor = conn.cursor() + cursor.execute("CREATE TABLE hello(id int, value varchar(256))") + cursor.execute("INSERT INTO hello values(1, 'hello'), (2, 'ciao')") + cursor.close() + conn.commit() + conn.close() + + # Use `handler()` on initialize database + Postgresql = testing.postgresql.PostgresqlFactory(cache_initialized_db=True, + on_initialized=handler) + + + Requirements + ============ + * Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 + * pg8000 1.10 + + License + ======= + Apache License 2.0 + + + History + ======= + + 1.3.0 (2016-02-03) + ------------------- + * Add testing.postgresql.PostgresqlFactory + * Depend on ``testing.common.database`` package + + 1.2.1 (2015-08-22) + ------------------- + * Fix bug: + + - Close #3 Fix AttributeError on end of tests + + 1.2.0 (2015-05-17) + ------------------- + * Use `pg8000` for connector to create test database + * Connect to `postgres` to create test database (instead of `template1`) + + 1.1.2 (2015-04-06) + ------------------- + * Fix bugs: + + - Do not call os.getpid() on destructor (if not needed) + - Raise detailed RuntimeError if initdb exits non-zero + + 1.1.1 (2015-01-18) + ------------------- + * Disable logging_collector feature (For Fedora) + * Fix bugs: + + - MacPorts default path is /opt/local/lib/postgresql*, no dash + + 1.1.0 (2014-12-20) + ------------------- + * Invoke 'postgres' command instead of 'postmaster' + + 1.0.6 (2014-07-19) + ------------------- + * Fix #1 Dirty postmaster shut down + + 1.0.5 (2014-07-19) + ------------------- + * Fix path for PostgreSQL + * Use absolute path for which command + + 1.0.4 (2014-06-19) + ------------------- + * Fix timeout on terminating postgresql + * Support PostgreSQL on /usr/local/bin (cf. FreeBSD ports) + * Fix bugs + + 1.0.3 (2014-06-11) + ------------------- + * Fix ImportError if caught SIGINT on py3 + + 1.0.2 (2013-12-06) + ------------------- + * Change behavior: Postgresql#stop() cleans workdir + * Fix caught AttributeError on object deletion + + 1.0.1 (2013-12-05) + ------------------- + * Add @skipIfNotInstalled decorator (alias of skipIfNotFound) + * Suport python 2.6 and 3.2 + + 1.0.0 (2013-12-04) + ------------------- + * Add @skipIfNotFound decorator + + 0.1.0 (2013-11-26) + ------------------- + * First release + +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Topic :: Database +Classifier: Topic :: Software Development +Classifier: Topic :: Software Development :: Testing diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..b3cfc22 --- /dev/null +++ b/README.rst @@ -0,0 +1,206 @@ +About +===== +``testing.postgresql`` automatically setups a postgresql instance in a temporary directory, and destroys it after testing. + +.. image:: https://travis-ci.org/tk0miya/testing.postgresql.svg?branch=master + :target: https://travis-ci.org/tk0miya/testing.postgresql + +.. image:: https://coveralls.io/repos/tk0miya/testing.postgresql/badge.png?branch=master + :target: https://coveralls.io/r/tk0miya/testing.postgresql?branch=master + +.. image:: https://codeclimate.com/github/tk0miya/testing.postgresql/badges/gpa.svg + :target: https://codeclimate.com/github/tk0miya/testing.postgresql + + +Documentation + https://github.com/tk0miya/testing.postgresql +Issues + https://github.com/tk0miya/testing.postgresql/issues +Download + https://pypi.python.org/pypi/testing.postgresql + +Install +======= +Use pip:: + + $ pip install testing.postgresql + +And ``testing.postgresql`` requires PostgreSQL server in your PATH. + + +Usage +===== +Create PostgreSQL instance using ``testing.postgresql.Postgresql``:: + + import testing.postgresql + from sqlalchemy import create_engine + + # Lanuch new PostgreSQL server + with testing.postgresql.Postgresql() as postgresql: + # connect to PostgreSQL + engine = create_engine(postgresql.url()) + + # if you use postgresql or other drivers: + # import psycopg2 + # db = psycopg2.connect(**postgresql.dsn()) + + # + # do any tests using PostgreSQL... + # + + # PostgreSQL server is terminated here + + +``testing.postgresql.Postgresql`` executes ``initdb`` and ``postgres`` on instantiation. +On deleting Postgresql object, it terminates PostgreSQL instance and removes temporary directory. + +If you want a database including tables and any fixtures for your apps, +use ``copy_data_from`` keyword:: + + # uses a copy of specified data directory of PostgreSQL. + postgresql = testing.postgresql.Postgresql(copy_data_from='/path/to/your/database') + + +For example, you can setup new PostgreSQL server for each testcases on setUp() method:: + + import unittest + import testing.postgresql + + class MyTestCase(unittest.TestCase): + def setUp(self): + self.postgresql = testing.postgresql.Postgresql() + + def tearDown(self): + self.postgresql.stop() + + +To make your tests faster +------------------------- + +``testing.postgresql.Postgresql`` invokes ``initdb`` command on every instantiation. +That is very simple. But, in many cases, it is very waste that generating brandnew database for each testcase. + +To optimize the behavior, use ``testing.postgresql.PostgresqlFactory``. +The factory class is able to cache the generated database beyond the testcases, +and it reduces the number of invocation of ``initdb`` command:: + + import unittest + import testing.postgresql + + # Generate Postgresql class which shares the generated database + Postgresql = testing.postgresql.PostgresqlFactory(cache_initialized_db=True) + + + def tearDownModule(self): + # clear cached database at end of tests + Postgresql.clear_cache() + + + class MyTestCase(unittest.TestCase): + def setUp(self): + # Use the generated Postgresql class instead of testing.postgresql.Postgresql + self.postgresql = Postgresql() + + def tearDown(self): + self.postgresql.stop() + +If you want to insert fixtures to the cached database, use ``initdb_handler`` option:: + + # create initial data on create as fixtures into the database + def handler(postgresql): + conn = psycopg2.connect(**postgresql.dsn()) + cursor = conn.cursor() + cursor.execute("CREATE TABLE hello(id int, value varchar(256))") + cursor.execute("INSERT INTO hello values(1, 'hello'), (2, 'ciao')") + cursor.close() + conn.commit() + conn.close() + + # Use `handler()` on initialize database + Postgresql = testing.postgresql.PostgresqlFactory(cache_initialized_db=True, + on_initialized=handler) + + +Requirements +============ +* Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 +* pg8000 1.10 + +License +======= +Apache License 2.0 + + +History +======= + +1.3.0 (2016-02-03) +------------------- +* Add testing.postgresql.PostgresqlFactory +* Depend on ``testing.common.database`` package + +1.2.1 (2015-08-22) +------------------- +* Fix bug: + + - Close #3 Fix AttributeError on end of tests + +1.2.0 (2015-05-17) +------------------- +* Use `pg8000` for connector to create test database +* Connect to `postgres` to create test database (instead of `template1`) + +1.1.2 (2015-04-06) +------------------- +* Fix bugs: + + - Do not call os.getpid() on destructor (if not needed) + - Raise detailed RuntimeError if initdb exits non-zero + +1.1.1 (2015-01-18) +------------------- +* Disable logging_collector feature (For Fedora) +* Fix bugs: + + - MacPorts default path is /opt/local/lib/postgresql*, no dash + +1.1.0 (2014-12-20) +------------------- +* Invoke 'postgres' command instead of 'postmaster' + +1.0.6 (2014-07-19) +------------------- +* Fix #1 Dirty postmaster shut down + +1.0.5 (2014-07-19) +------------------- +* Fix path for PostgreSQL +* Use absolute path for which command + +1.0.4 (2014-06-19) +------------------- +* Fix timeout on terminating postgresql +* Support PostgreSQL on /usr/local/bin (cf. FreeBSD ports) +* Fix bugs + +1.0.3 (2014-06-11) +------------------- +* Fix ImportError if caught SIGINT on py3 + +1.0.2 (2013-12-06) +------------------- +* Change behavior: Postgresql#stop() cleans workdir +* Fix caught AttributeError on object deletion + +1.0.1 (2013-12-05) +------------------- +* Add @skipIfNotInstalled decorator (alias of skipIfNotFound) +* Suport python 2.6 and 3.2 + +1.0.0 (2013-12-04) +------------------- +* Add @skipIfNotFound decorator + +0.1.0 (2013-11-26) +------------------- +* First release diff --git a/debian/.git-dpm b/debian/.git-dpm new file mode 100644 index 0000000..846ade9 --- /dev/null +++ b/debian/.git-dpm @@ -0,0 +1,11 @@ +# see git-dpm(1) from git-dpm package +659829a63f423c495e6fbcc7a053235426234513 +659829a63f423c495e6fbcc7a053235426234513 +b427257b8230a01eb8c01154fc1cc8f9f67a5419 +b427257b8230a01eb8c01154fc1cc8f9f67a5419 +python-testing.postgresql_1.3.0.orig.tar.gz +0e08d0bc0b958f90bd30f2d75087a946912af012 +11000 +debianTag="debian/%e%v" +patchedTag="patched/%e%v" +upstreamTag="upstream/%e%u" diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..dd953b5 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +python-testing.postgresql (1.3.0-1) unstable; urgency=low + + * Initial release. (Closes: #838582) + + -- Dominik George <nik@naturalnet.de> Thu, 13 Oct 2016 13:56:54 +0200 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..f599e28 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +10 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..65f24b1 --- /dev/null +++ b/debian/control @@ -0,0 +1,61 @@ +Source: python-testing.postgresql +Maintainer: Debian Python Modules Team <python-modules-team@lists.alioth.debian.org> +Uploaders: + Dominik George <nik@naturalnet.de>, +Section: python +Priority: optional +Build-Depends: + debhelper (>= 10), + dh-python, + postgresql, + python-all (>= 2.6.6-3), + python-nose, + python-pg8000 (>= 1.10), + python-psycopg2, + python-setuptools (>= 0.6b3), + python-sqlalchemy, + python-testing.common.database (>= 1.1.0), + python3-all, + python3-nose, + python3-pg8000 (>= 1.10), + python3-psycopg2, + python3-setuptools, + python3-sqlalchemy, + python3-testing.common.database (>= 1.1.0), +Standards-Version: 3.9.8 +Homepage: https://github.com/tk0miya/testing.postgresql +Testsuite: autopkgtest-pkg-python +Vcs-Git: https://anonscm.debian.org/git/python-modules/packages/python-testing.postgresql.git +Vcs-Browser: https://anonscm.debian.org/git/python-modules/packages/python-testing.postgresql.git + +Package: python-testing.postgresql +Architecture: all +Depends: + postgresql, + ${misc:Depends}, + ${python:Depends}, +Description: Python testing.* framework - PostgreSQL helpers (Python 2) + The testing.* Python modules provide a framework for Python unit test suites + that need to run tests against servers, like network-based DBMSs. + . + testing.postgresql contains helper classes to work with a temporary + PostgreSQL server. It automatically sets up a PostgreSQL instance in a + temporary directory, and destroys it after testing. + . + This package contains the Python 2 version. + +Package: python3-testing.postgresql +Architecture: all +Depends: + postgresql, + ${misc:Depends}, + ${python3:Depends}, +Description: Python testing.* framework - PostgreSQL helpers (Python 3) + The testing.* Python modules provide a framework for Python unit test suites + that need to run tests against servers, like network-based DBMSs. + . + testing.postgresql contains helper classes to work with a temporary + PostgreSQL server. It automatically sets up a PostgreSQL instance in a + temporary directory, and destroys it after testing. + . + This package contains the Python 3 version. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..2402168 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,28 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: testing.postgresql +Upstream-Contact: Takeshi KOMIYA <i.tkomiya@gmail.com> +Source: https://github.com/tk0miya/testing.postgresql + +Files: * +Copyright: 2013 Takeshi KOMIYA <i.tkomiya@gmail.com> +License: Apache-2.0 + +Files: debian/* +Copyright: 2016 Dominik George <nik@naturalnet.de> +License: Apache-2.0 + +License: Apache-2.0 + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + . + http://www.apache.org/licenses/LICENSE-2.0 + . + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + . + On Debian systems, you can find the Apache-2.0 license under + /usr/share/common-licenses/Apache-2.0 . diff --git a/debian/patches/0001-From-Takeshi-KOMIYA-i.tkomiya-gmail.com.patch b/debian/patches/0001-From-Takeshi-KOMIYA-i.tkomiya-gmail.com.patch new file mode 100644 index 0000000..2dd13b7 --- /dev/null +++ b/debian/patches/0001-From-Takeshi-KOMIYA-i.tkomiya-gmail.com.patch @@ -0,0 +1,129 @@ +From 659829a63f423c495e6fbcc7a053235426234513 Mon Sep 17 00:00:00 2001 +From: Dominik George <nik@naturalnet.de> +Date: Thu, 13 Oct 2016 13:40:10 +0200 +Subject: =?UTF-8?q?From:=20Takeshi=20KOMIYA=20<i.tkomiya@gmail.com>=0ADate?= + =?UTF-8?q?:=20Fri,=205=20Feb=202016=2021:56:57=20+0900=0ASubject:=20[PATC?= + =?UTF-8?q?H]=20Use=20utility=20methods=20of=20testing.common.database=20>?= + =?UTF-8?q?=3D=201.1.0=0AOrigin:=20upstream,=20https://github.com/tk0miya/?= + =?UTF-8?q?testing.postgresql/commit/738c8eb19a4b064dd74ff851c379dd1cbf11b?= + =?UTF-8?q?c65.patch=0AApplied-Upstream:=20commit:738c8eb19a4b064dd74ff851?= + =?UTF-8?q?c379dd1cbf11bc65?= + +--- + setup.py | 2 +- + tests/test_postgresql.py | 41 ++++++++++++++--------------------------- + 2 files changed, 15 insertions(+), 28 deletions(-) + +diff --git a/setup.py b/setup.py +index 08095cd..9e95298 100644 +--- a/setup.py ++++ b/setup.py +@@ -18,7 +18,7 @@ classifiers = [ + "Topic :: Software Development :: Testing", + ] + +-install_requires = ['testing.common.database', 'pg8000 >= 1.10'] ++install_requires = ['testing.common.database >= 1.1.0', 'pg8000 >= 1.10'] + if sys.version_info < (2, 7): + install_requires.append('unittest2') + +diff --git a/tests/test_postgresql.py b/tests/test_postgresql.py +index 29e2b7e..b3603a2 100644 +--- a/tests/test_postgresql.py ++++ b/tests/test_postgresql.py +@@ -47,38 +47,31 @@ class TestPostgresql(unittest.TestCase): + conn.close() + finally: + # shutting down +- pid = pgsql.pid +- self.assertTrue(pid) +- os.kill(pid, 0) # process is alive ++ pid = pgsql.server_pid ++ self.assertTrue(pgsql.is_alive()) + + pgsql.stop() + sleep(1) + +- self.assertIsNone(pgsql.pid) ++ self.assertFalse(pgsql.is_alive()) + with self.assertRaises(OSError): + os.kill(pid, 0) # process is down + + def test_stop(self): + # start postgresql server + pgsql = testing.postgresql.Postgresql() +- self.assertIsNotNone(pgsql.pid) + self.assertTrue(os.path.exists(pgsql.base_dir)) +- pid = pgsql.pid +- os.kill(pid, 0) # process is alive ++ self.assertTrue(pgsql.is_alive()) + + # call stop() + pgsql.stop() +- self.assertIsNone(pgsql.pid) + self.assertFalse(os.path.exists(pgsql.base_dir)) +- with self.assertRaises(OSError): +- os.kill(pid, 0) # process is down ++ self.assertFalse(pgsql.is_alive()) + + # call stop() again + pgsql.stop() +- self.assertIsNone(pgsql.pid) + self.assertFalse(os.path.exists(pgsql.base_dir)) +- with self.assertRaises(OSError): +- os.kill(pid, 0) # process is down ++ self.assertFalse(pgsql.is_alive()) + + # delete postgresql object after stop() + del pgsql +@@ -98,20 +91,17 @@ class TestPostgresql(unittest.TestCase): + self.assertIsNotNone(conn) + conn.close() + +- pid = pgsql.pid +- os.kill(pid, 0) # process is alive ++ self.assertTrue(pgsql.is_alive()) + +- self.assertIsNone(pgsql.pid) +- with self.assertRaises(OSError): +- os.kill(pid, 0) # process is down ++ self.assertFalse(pgsql.is_alive()) + + def test_multiple_postgresql(self): + pgsql1 = testing.postgresql.Postgresql() + pgsql2 = testing.postgresql.Postgresql() +- self.assertNotEqual(pgsql1.pid, pgsql2.pid) ++ self.assertNotEqual(pgsql1.server_pid, pgsql2.server_pid) + +- os.kill(pgsql1.pid, 0) # process is alive +- os.kill(pgsql2.pid, 0) # process is alive ++ self.assertTrue(pgsql1.is_alive()) ++ self.assertTrue(pgsql2.is_alive()) + + def test_postgresql_is_not_found(self): + try: +@@ -135,21 +125,18 @@ class TestPostgresql(unittest.TestCase): + else: + os.wait() + sleep(1) +- self.assertTrue(pgsql.pid) +- os.kill(pgsql.pid, 0) # process is alive (delete pgsql obj in child does not effect) ++ self.assertTrue(pgsql.is_alive()) # process is alive (delete pgsql obj in child does not effect) + + def test_stop_on_child_process(self): + pgsql = testing.postgresql.Postgresql() + if os.fork() == 0: + pgsql.stop() +- self.assertTrue(pgsql.pid) +- os.kill(pgsql.pid, 0) # process is alive (calling stop() is ignored) ++ os.kill(pgsql.server_pid, 0) # process is alive (calling stop() is ignored) + os.kill(os.getpid(), signal.SIGTERM) # exit tests FORCELY + else: + os.wait() + sleep(1) +- self.assertTrue(pgsql.pid) +- os.kill(pgsql.pid, 0) # process is alive (calling stop() in child is ignored) ++ self.assertTrue(pgsql.is_alive()) # process is alive (calling stop() in child is ignored) + + def test_copy_data_from(self): + try: diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..7bddafa --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +0001-From-Takeshi-KOMIYA-i.tkomiya-gmail.com.patch diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..5a940b1 --- /dev/null +++ b/debian/rules @@ -0,0 +1,12 @@ +#!/usr/bin/make -f + +export PYBUILD_NAME=testing.postgresql +%: + dh $@ --with python2,python3 --buildsystem=pybuild + +override_dh_installchangelogs: + csplit README.rst '%^History$$%' + dh_installchangelogs xx00 + +override_dh_clean: + dh_clean xx00 diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/debian/source/lintian-overrides b/debian/source/lintian-overrides new file mode 100644 index 0000000..32d5859 --- /dev/null +++ b/debian/source/lintian-overrides @@ -0,0 +1,2 @@ +# Upstream does not sign tarballs. Discussing that, though. +python-testing.postgresql source: debian-watch-may-check-gpg-signature diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..b932fb2 --- /dev/null +++ b/debian/watch @@ -0,0 +1,4 @@ +# please also check http://pypi.debian.net/testing.postgresql/watch +version=3 +opts="uversionmangle=s/(rc|a|b|c)/~$1/" \ +http://pypi.debian.net/testing.postgresql/testing.postgresql-(.+)\.(?:zip|tgz|tbz|txz|(?:tar\.(?:gz|bz2|xz))) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..fe6c155 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,25 @@ +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 + +[build] +build-base = _build + +[sdist] +formats = gztar + +[wheel] +universal = 1 + +[aliases] +release = check -r -s register sdist bdist_wheel upload + +[check] +strict = 1 +restructuredtext = 1 + +[flake8] +max-line-length = 120 +ignore = _ + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..08095cd --- /dev/null +++ b/setup.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +import sys +from setuptools import setup, find_packages + +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.2", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Topic :: Database", + "Topic :: Software Development", + "Topic :: Software Development :: Testing", +] + +install_requires = ['testing.common.database', 'pg8000 >= 1.10'] +if sys.version_info < (2, 7): + install_requires.append('unittest2') + + +setup( + name='testing.postgresql', + version='1.3.0', + description='automatically setups a postgresql instance in a temporary ' + 'directory, and destroys it after testing', + long_description=open('README.rst').read(), + classifiers=classifiers, + keywords=[], + author='Takeshi Komiya', + author_email='i.tkomiya at gmail.com', + url='https://github.com/tk0miya/testing.postgresql', + license='Apache License 2.0', + packages=find_packages('src'), + package_dir={'': 'src'}, + package_data={'': ['buildout.cfg']}, + include_package_data=True, + install_requires=install_requires, + extras_require=dict( + testing=[ + 'nose', + 'psycopg2', + 'SQLAlchemy', + ], + ), + test_suite='nose.collector', + namespace_packages=['testing'], +) diff --git a/src/testing.postgresql.egg-info/PKG-INFO b/src/testing.postgresql.egg-info/PKG-INFO new file mode 100644 index 0000000..45dcb11 --- /dev/null +++ b/src/testing.postgresql.egg-info/PKG-INFO @@ -0,0 +1,229 @@ +Metadata-Version: 1.1 +Name: testing.postgresql +Version: 1.3.0 +Summary: automatically setups a postgresql instance in a temporary directory, and destroys it after testing +Home-page: https://github.com/tk0miya/testing.postgresql +Author: Takeshi Komiya +Author-email: i.tkomiya at gmail.com +License: Apache License 2.0 +Description: About + ===== + ``testing.postgresql`` automatically setups a postgresql instance in a temporary directory, and destroys it after testing. + + .. image:: https://travis-ci.org/tk0miya/testing.postgresql.svg?branch=master + :target: https://travis-ci.org/tk0miya/testing.postgresql + + .. image:: https://coveralls.io/repos/tk0miya/testing.postgresql/badge.png?branch=master + :target: https://coveralls.io/r/tk0miya/testing.postgresql?branch=master + + .. image:: https://codeclimate.com/github/tk0miya/testing.postgresql/badges/gpa.svg + :target: https://codeclimate.com/github/tk0miya/testing.postgresql + + + Documentation + https://github.com/tk0miya/testing.postgresql + Issues + https://github.com/tk0miya/testing.postgresql/issues + Download + https://pypi.python.org/pypi/testing.postgresql + + Install + ======= + Use pip:: + + $ pip install testing.postgresql + + And ``testing.postgresql`` requires PostgreSQL server in your PATH. + + + Usage + ===== + Create PostgreSQL instance using ``testing.postgresql.Postgresql``:: + + import testing.postgresql + from sqlalchemy import create_engine + + # Lanuch new PostgreSQL server + with testing.postgresql.Postgresql() as postgresql: + # connect to PostgreSQL + engine = create_engine(postgresql.url()) + + # if you use postgresql or other drivers: + # import psycopg2 + # db = psycopg2.connect(**postgresql.dsn()) + + # + # do any tests using PostgreSQL... + # + + # PostgreSQL server is terminated here + + + ``testing.postgresql.Postgresql`` executes ``initdb`` and ``postgres`` on instantiation. + On deleting Postgresql object, it terminates PostgreSQL instance and removes temporary directory. + + If you want a database including tables and any fixtures for your apps, + use ``copy_data_from`` keyword:: + + # uses a copy of specified data directory of PostgreSQL. + postgresql = testing.postgresql.Postgresql(copy_data_from='/path/to/your/database') + + + For example, you can setup new PostgreSQL server for each testcases on setUp() method:: + + import unittest + import testing.postgresql + + class MyTestCase(unittest.TestCase): + def setUp(self): + self.postgresql = testing.postgresql.Postgresql() + + def tearDown(self): + self.postgresql.stop() + + + To make your tests faster + ------------------------- + + ``testing.postgresql.Postgresql`` invokes ``initdb`` command on every instantiation. + That is very simple. But, in many cases, it is very waste that generating brandnew database for each testcase. + + To optimize the behavior, use ``testing.postgresql.PostgresqlFactory``. + The factory class is able to cache the generated database beyond the testcases, + and it reduces the number of invocation of ``initdb`` command:: + + import unittest + import testing.postgresql + + # Generate Postgresql class which shares the generated database + Postgresql = testing.postgresql.PostgresqlFactory(cache_initialized_db=True) + + + def tearDownModule(self): + # clear cached database at end of tests + Postgresql.clear_cache() + + + class MyTestCase(unittest.TestCase): + def setUp(self): + # Use the generated Postgresql class instead of testing.postgresql.Postgresql + self.postgresql = Postgresql() + + def tearDown(self): + self.postgresql.stop() + + If you want to insert fixtures to the cached database, use ``initdb_handler`` option:: + + # create initial data on create as fixtures into the database + def handler(postgresql): + conn = psycopg2.connect(**postgresql.dsn()) + cursor = conn.cursor() + cursor.execute("CREATE TABLE hello(id int, value varchar(256))") + cursor.execute("INSERT INTO hello values(1, 'hello'), (2, 'ciao')") + cursor.close() + conn.commit() + conn.close() + + # Use `handler()` on initialize database + Postgresql = testing.postgresql.PostgresqlFactory(cache_initialized_db=True, + on_initialized=handler) + + + Requirements + ============ + * Python 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 + * pg8000 1.10 + + License + ======= + Apache License 2.0 + + + History + ======= + + 1.3.0 (2016-02-03) + ------------------- + * Add testing.postgresql.PostgresqlFactory + * Depend on ``testing.common.database`` package + + 1.2.1 (2015-08-22) + ------------------- + * Fix bug: + + - Close #3 Fix AttributeError on end of tests + + 1.2.0 (2015-05-17) + ------------------- + * Use `pg8000` for connector to create test database + * Connect to `postgres` to create test database (instead of `template1`) + + 1.1.2 (2015-04-06) + ------------------- + * Fix bugs: + + - Do not call os.getpid() on destructor (if not needed) + - Raise detailed RuntimeError if initdb exits non-zero + + 1.1.1 (2015-01-18) + ------------------- + * Disable logging_collector feature (For Fedora) + * Fix bugs: + + - MacPorts default path is /opt/local/lib/postgresql*, no dash + + 1.1.0 (2014-12-20) + ------------------- + * Invoke 'postgres' command instead of 'postmaster' + + 1.0.6 (2014-07-19) + ------------------- + * Fix #1 Dirty postmaster shut down + + 1.0.5 (2014-07-19) + ------------------- + * Fix path for PostgreSQL + * Use absolute path for which command + + 1.0.4 (2014-06-19) + ------------------- + * Fix timeout on terminating postgresql + * Support PostgreSQL on /usr/local/bin (cf. FreeBSD ports) + * Fix bugs + + 1.0.3 (2014-06-11) + ------------------- + * Fix ImportError if caught SIGINT on py3 + + 1.0.2 (2013-12-06) + ------------------- + * Change behavior: Postgresql#stop() cleans workdir + * Fix caught AttributeError on object deletion + + 1.0.1 (2013-12-05) + ------------------- + * Add @skipIfNotInstalled decorator (alias of skipIfNotFound) + * Suport python 2.6 and 3.2 + + 1.0.0 (2013-12-04) + ------------------- + * Add @skipIfNotFound decorator + + 0.1.0 (2013-11-26) + ------------------- + * First release + +Platform: UNKNOWN +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.2 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Topic :: Database +Classifier: Topic :: Software Development +Classifier: Topic :: Software Development :: Testing diff --git a/src/testing.postgresql.egg-info/SOURCES.txt b/src/testing.postgresql.egg-info/SOURCES.txt new file mode 100644 index 0000000..7d599dc --- /dev/null +++ b/src/testing.postgresql.egg-info/SOURCES.txt @@ -0,0 +1,14 @@ +LICENSE +MANIFEST.in +README.rst +setup.cfg +setup.py +src/testing/__init__.py +src/testing/postgresql.py +src/testing.postgresql.egg-info/PKG-INFO +src/testing.postgresql.egg-info/SOURCES.txt +src/testing.postgresql.egg-info/dependency_links.txt +src/testing.postgresql.egg-info/namespace_packages.txt +src/testing.postgresql.egg-info/requires.txt +src/testing.postgresql.egg-info/top_level.txt +tests/test_postgresql.py
\ No newline at end of file diff --git a/src/testing.postgresql.egg-info/dependency_links.txt b/src/testing.postgresql.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/testing.postgresql.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/testing.postgresql.egg-info/namespace_packages.txt b/src/testing.postgresql.egg-info/namespace_packages.txt new file mode 100644 index 0000000..038d718 --- /dev/null +++ b/src/testing.postgresql.egg-info/namespace_packages.txt @@ -0,0 +1 @@ +testing diff --git a/src/testing.postgresql.egg-info/requires.txt b/src/testing.postgresql.egg-info/requires.txt new file mode 100644 index 0000000..033c513 --- /dev/null +++ b/src/testing.postgresql.egg-info/requires.txt @@ -0,0 +1,7 @@ +testing.common.database +pg8000 >= 1.10 + +[testing] +nose +psycopg2 +SQLAlchemy diff --git a/src/testing.postgresql.egg-info/top_level.txt b/src/testing.postgresql.egg-info/top_level.txt new file mode 100644 index 0000000..038d718 --- /dev/null +++ b/src/testing.postgresql.egg-info/top_level.txt @@ -0,0 +1 @@ +testing diff --git a/src/testing/__init__.py b/src/testing/__init__.py new file mode 100644 index 0000000..c4fb22e --- /dev/null +++ b/src/testing/__init__.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright 2013 Takeshi KOMIYA +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__import__('pkg_resources').declare_namespace(__name__) diff --git a/src/testing/postgresql.py b/src/testing/postgresql.py new file mode 100644 index 0000000..948fc3c --- /dev/null +++ b/src/testing/postgresql.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- +# Copyright 2013 Takeshi KOMIYA +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import pg8000 +import signal +import subprocess +from glob import glob +from contextlib import closing + +from testing.common.database import ( + Database, DatabaseFactory, get_path_of, SkipIfNotInstalledDecorator +) + + +__all__ = ['Postgresql', 'skipIfNotFound'] + +SEARCH_PATHS = (['/usr/local/pgsql', '/usr/local'] + + glob('/usr/lib/postgresql/*') + # for Debian/Ubuntu + glob('/opt/local/lib/postgresql*')) # for MacPorts + + +class Postgresql(Database): + DEFAULT_SETTINGS = dict(auto_start=2, + base_dir=None, + initdb=None, + initdb_args='-U postgres -A trust', + postgres=None, + postgres_args='-h 127.0.0.1 -F -c logging_collector=off', + pid=None, + port=None, + copy_data_from=None) + subdirectories = ['data', 'tmp'] + + def initialize(self): + self.initdb = self.settings.pop('initdb') + if self.initdb is None: + self.initdb = find_program('initdb', ['bin']) + + self.postgres = self.settings.pop('postgres') + if self.postgres is None: + self.postgres = find_program('postgres', ['bin']) + + def dsn(self, **kwargs): + # "database=test host=localhost user=postgres" + params = dict(kwargs) + params.setdefault('port', self.settings['port']) + params.setdefault('host', '127.0.0.1') + params.setdefault('user', 'postgres') + params.setdefault('database', 'test') + + return params + + def url(self, **kwargs): + params = self.dsn(**kwargs) + + url = ('postgresql://%s@%s:%d/%s' % + (params['user'], params['host'], params['port'], params['database'])) + + return url + + def get_data_directory(self): + return os.path.join(self.base_dir, 'data') + + def initialize_database(self): + if not os.path.exists(os.path.join(self.base_dir, 'data', 'PG_VERSION')): + args = ([self.initdb, '-D', os.path.join(self.base_dir, 'data'), '--lc-messages=C'] + + self.settings['initdb_args'].split()) + + try: + p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output, err = p.communicate() + if p.returncode != 0: + raise RuntimeError("initdb failed: %r" % err) + except OSError as exc: + raise RuntimeError("failed to spawn initdb: %s" % exc) + + def get_server_commandline(self): + return ([self.postgres, + '-p', str(self.settings['port']), + '-D', os.path.join(self.base_dir, 'data'), + '-k', os.path.join(self.base_dir, 'tmp')] + + self.settings['postgres_args'].split()) + + def poststart(self): + with closing(pg8000.connect(**self.dsn(database='postgres'))) as conn: + conn.autocommit = True + with closing(conn.cursor()) as cursor: + cursor.execute("SELECT COUNT(*) FROM pg_database WHERE datname='test'") + if cursor.fetchone()[0] <= 0: + cursor.execute('CREATE DATABASE test') + + def is_server_available(self): + try: + with closing(pg8000.connect(**self.dsn(database='template1'))): + pass + except pg8000.Error: + return False + else: + return True + + def terminate(self, *args): + # send SIGINT instead of SIGTERM + super(Postgresql, self).terminate(signal.SIGINT) + + +class PostgresqlFactory(DatabaseFactory): + target_class = Postgresql + + +class PostgresqlSkipIfNotInstalledDecorator(SkipIfNotInstalledDecorator): + name = 'PostgreSQL' + + def search_server(self): + find_program('postgres', ['bin']) + + +skipIfNotFound = skipIfNotInstalled = PostgresqlSkipIfNotInstalledDecorator() + + +def find_program(name, subdirs): + path = get_path_of(name) + if path: + return path + + for base_dir in SEARCH_PATHS: + for subdir in subdirs: + path = os.path.join(base_dir, subdir, name) + if os.path.exists(path): + return path + + raise RuntimeError("command not found: %s" % name) diff --git a/tests/test_postgresql.py b/tests/test_postgresql.py new file mode 100644 index 0000000..29e2b7e --- /dev/null +++ b/tests/test_postgresql.py @@ -0,0 +1,282 @@ +# -*- coding: utf-8 -*- + +import os +import sys +import signal +import tempfile +import testing.postgresql +from time import sleep +from shutil import rmtree +import pg8000 +import psycopg2 +import sqlalchemy +from contextlib import closing + +if sys.version_info < (2, 7): + import unittest2 as unittest +else: + import unittest + + +class TestPostgresql(unittest.TestCase): + def test_basic(self): + try: + # start postgresql server + pgsql = testing.postgresql.Postgresql() + self.assertIsNotNone(pgsql) + params = pgsql.dsn() + self.assertEqual('test', params['database']) + self.assertEqual('127.0.0.1', params['host']) + self.assertEqual(pgsql.settings['port'], params['port']) + self.assertEqual('postgres', params['user']) + + # connect to postgresql (w/ psycopg2) + conn = psycopg2.connect(**pgsql.dsn()) + self.assertIsNotNone(conn) + self.assertRegexpMatches(pgsql.read_bootlog(), 'is ready to accept connections') + conn.close() + + # connect to postgresql (w/ sqlalchemy) + engine = sqlalchemy.create_engine(pgsql.url()) + self.assertIsNotNone(engine) + + # connect to postgresql (w/ pg8000) + conn = pg8000.connect(**pgsql.dsn()) + self.assertIsNotNone(conn) + self.assertRegexpMatches(pgsql.read_bootlog(), 'is ready to accept connections') + conn.close() + finally: + # shutting down + pid = pgsql.pid + self.assertTrue(pid) + os.kill(pid, 0) # process is alive + + pgsql.stop() + sleep(1) + + self.assertIsNone(pgsql.pid) + with self.assertRaises(OSError): + os.kill(pid, 0) # process is down + + def test_stop(self): + # start postgresql server + pgsql = testing.postgresql.Postgresql() + self.assertIsNotNone(pgsql.pid) + self.assertTrue(os.path.exists(pgsql.base_dir)) + pid = pgsql.pid + os.kill(pid, 0) # process is alive + + # call stop() + pgsql.stop() + self.assertIsNone(pgsql.pid) + self.assertFalse(os.path.exists(pgsql.base_dir)) + with self.assertRaises(OSError): + os.kill(pid, 0) # process is down + + # call stop() again + pgsql.stop() + self.assertIsNone(pgsql.pid) + self.assertFalse(os.path.exists(pgsql.base_dir)) + with self.assertRaises(OSError): + os.kill(pid, 0) # process is down + + # delete postgresql object after stop() + del pgsql + + def test_dsn_and_url(self): + pgsql = testing.postgresql.Postgresql(port=12345, auto_start=0) + self.assertEqual({'database': 'test', 'host': '127.0.0.1', 'port': 12345, 'user': 'postgres'}, + pgsql.dsn()) + self.assertEqual("postgresql://postgres@127.0.0.1:12345/test", pgsql.url()) + + def test_with_statement(self): + with testing.postgresql.Postgresql() as pgsql: + self.assertIsNotNone(pgsql) + + # connect to postgresql + conn = pg8000.connect(**pgsql.dsn()) + self.assertIsNotNone(conn) + conn.close() + + pid = pgsql.pid + os.kill(pid, 0) # process is alive + + self.assertIsNone(pgsql.pid) + with self.assertRaises(OSError): + os.kill(pid, 0) # process is down + + def test_multiple_postgresql(self): + pgsql1 = testing.postgresql.Postgresql() + pgsql2 = testing.postgresql.Postgresql() + self.assertNotEqual(pgsql1.pid, pgsql2.pid) + + os.kill(pgsql1.pid, 0) # process is alive + os.kill(pgsql2.pid, 0) # process is alive + + def test_postgresql_is_not_found(self): + try: + search_paths = testing.postgresql.SEARCH_PATHS + testing.postgresql.SEARCH_PATHS = [] + path_env = os.environ['PATH'] + os.environ['PATH'] = '' + + with self.assertRaises(RuntimeError): + testing.postgresql.Postgresql() + finally: + testing.postgresql.SEARCH_PATHS = search_paths + os.environ['PATH'] = path_env + + def test_fork(self): + pgsql = testing.postgresql.Postgresql() + if os.fork() == 0: + del pgsql + pgsql = None + os.kill(os.getpid(), signal.SIGTERM) # exit tests FORCELY + else: + os.wait() + sleep(1) + self.assertTrue(pgsql.pid) + os.kill(pgsql.pid, 0) # process is alive (delete pgsql obj in child does not effect) + + def test_stop_on_child_process(self): + pgsql = testing.postgresql.Postgresql() + if os.fork() == 0: + pgsql.stop() + self.assertTrue(pgsql.pid) + os.kill(pgsql.pid, 0) # process is alive (calling stop() is ignored) + os.kill(os.getpid(), signal.SIGTERM) # exit tests FORCELY + else: + os.wait() + sleep(1) + self.assertTrue(pgsql.pid) + os.kill(pgsql.pid, 0) # process is alive (calling stop() in child is ignored) + + def test_copy_data_from(self): + try: + tmpdir = tempfile.mkdtemp() + + # create new database + with testing.postgresql.Postgresql(base_dir=tmpdir) as pgsql: + conn = pg8000.connect(**pgsql.dsn()) + with closing(conn.cursor()) as cursor: + cursor.execute("CREATE TABLE hello(id int, value varchar(256))") + cursor.execute("INSERT INTO hello values(1, 'hello'), (2, 'ciao')") + conn.commit() + conn.close() + + # create another database from first one + data_dir = os.path.join(tmpdir, 'data') + with testing.postgresql.Postgresql(copy_data_from=data_dir) as pgsql: + conn = pg8000.connect(**pgsql.dsn()) + with closing(conn.cursor()) as cursor: + cursor.execute('SELECT * FROM hello ORDER BY id') + self.assertEqual(cursor.fetchall(), ([1, 'hello'], [2, 'ciao'])) + conn.close() + finally: + rmtree(tmpdir) + + def test_skipIfNotInstalled_found(self): + try: + search_paths = testing.postgresql.SEARCH_PATHS + testing.postgresql.SEARCH_PATHS = [] + path_env = os.environ['PATH'] + os.environ['PATH'] = '' + + @testing.postgresql.skipIfNotInstalled + def testcase(): + pass + + self.assertEqual(True, hasattr(testcase, '__unittest_skip__')) + self.assertEqual(True, hasattr(testcase, '__unittest_skip_why__')) + self.assertEqual(True, testcase.__unittest_skip__) + self.assertEqual("PostgreSQL not found", testcase.__unittest_skip_why__) + finally: + testing.postgresql.SEARCH_PATHS = search_paths + os.environ['PATH'] = path_env + + def test_skipIfNotInstalled_notfound(self): + @testing.postgresql.skipIfNotInstalled + def testcase(): + pass + + self.assertEqual(False, hasattr(testcase, '__unittest_skip__')) + self.assertEqual(False, hasattr(testcase, '__unittest_skip_why__')) + + def test_skipIfNotInstalled_with_args_found(self): + path = testing.postgresql.find_program('postgres', ['bin']) + + @testing.postgresql.skipIfNotInstalled(path) + def testcase(): + pass + + self.assertEqual(False, hasattr(testcase, '__unittest_skip__')) + self.assertEqual(False, hasattr(testcase, '__unittest_skip_why__')) + + def test_skipIfNotInstalled_with_args_notfound(self): + @testing.postgresql.skipIfNotInstalled("/path/to/anywhere") + def testcase(): + pass + + self.assertEqual(True, hasattr(testcase, '__unittest_skip__')) + self.assertEqual(True, hasattr(testcase, '__unittest_skip_why__')) + self.assertEqual(True, testcase.__unittest_skip__) + self.assertEqual("PostgreSQL not found", testcase.__unittest_skip_why__) + + def test_skipIfNotFound_found(self): + try: + search_paths = testing.postgresql.SEARCH_PATHS + testing.postgresql.SEARCH_PATHS = [] + path_env = os.environ['PATH'] + os.environ['PATH'] = '' + + @testing.postgresql.skipIfNotFound + def testcase(): + pass + + self.assertEqual(True, hasattr(testcase, '__unittest_skip__')) + self.assertEqual(True, hasattr(testcase, '__unittest_skip_why__')) + self.assertEqual(True, testcase.__unittest_skip__) + self.assertEqual("PostgreSQL not found", testcase.__unittest_skip_why__) + finally: + testing.postgresql.SEARCH_PATHS = search_paths + os.environ['PATH'] = path_env + + def test_skipIfNotFound_notfound(self): + @testing.postgresql.skipIfNotFound + def testcase(): + pass + + self.assertEqual(False, hasattr(testcase, '__unittest_skip__')) + self.assertEqual(False, hasattr(testcase, '__unittest_skip_why__')) + + def test_PostgresqlFactory(self): + Postgresql = testing.postgresql.PostgresqlFactory(cache_initialized_db=True) + with Postgresql() as pgsql1: + self.assertTrue(pgsql1.settings['copy_data_from']) + copy_data_from1 = pgsql1.settings['copy_data_from'] + self.assertTrue(os.path.exists(copy_data_from1)) + with Postgresql() as pgsql2: + self.assertEqual(copy_data_from1, pgsql2.settings['copy_data_from']) + Postgresql.clear_cache() + self.assertFalse(os.path.exists(copy_data_from1)) + + def test_PostgresqlFactory_with_initialized_handler(self): + def handler(pgsql): + conn = pg8000.connect(**pgsql.dsn()) + with closing(conn.cursor()) as cursor: + cursor.execute("CREATE TABLE hello(id int, value varchar(256))") + cursor.execute("INSERT INTO hello values(1, 'hello'), (2, 'ciao')") + conn.commit() + conn.close() + + Postgresql = testing.postgresql.PostgresqlFactory(cache_initialized_db=True, + on_initialized=handler) + try: + with Postgresql() as pgsql: + conn = pg8000.connect(**pgsql.dsn()) + with closing(conn.cursor()) as cursor: + cursor.execute('SELECT * FROM hello ORDER BY id') + self.assertEqual(cursor.fetchall(), ([1, 'hello'], [2, 'ciao'])) + conn.close() + finally: + Postgresql.clear_cache() |
