├── .git-blame-ignore-revs ├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── .testr.conf ├── AUTHORS ├── Apache-2.0 ├── BSD ├── COPYING ├── ChangeLog ├── GOALS ├── HACKING ├── Makefile ├── NEWS ├── README.rst ├── fixtures ├── __init__.py ├── _fixtures │ ├── __init__.py │ ├── environ.py │ ├── logger.py │ ├── mockpatch.py │ ├── monkeypatch.py │ ├── packagepath.py │ ├── popen.py │ ├── pythonpackage.py │ ├── pythonpath.py │ ├── streams.py │ ├── tempdir.py │ ├── temphomedir.py │ ├── timeout.py │ └── warnings.py ├── callmany.py ├── fixture.py ├── testcase.py └── tests │ ├── __init__.py │ ├── _fixtures │ ├── __init__.py │ ├── test_environ.py │ ├── test_logger.py │ ├── test_mockpatch.py │ ├── test_monkeypatch.py │ ├── test_packagepath.py │ ├── test_popen.py │ ├── test_pythonpackage.py │ ├── test_pythonpath.py │ ├── test_streams.py │ ├── test_tempdir.py │ ├── test_temphomedir.py │ ├── test_timeout.py │ └── test_warnings.py │ ├── helpers.py │ ├── test_callmany.py │ ├── test_fixture.py │ └── test_testcase.py ├── pyproject.toml └── tox.ini /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | 9402d8dcdf7b0a1a1e004f56b7bcfc0070fa9f60 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | on: 4 | - push 5 | - pull_request 6 | jobs: 7 | test: 8 | name: Run unit tests 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | python: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "pypy-3.8", "pypy-3.9"] 13 | steps: 14 | - name: Checkout source code 15 | uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 18 | - name: Set up Python ${{ matrix.python }} 19 | uses: actions/setup-python@v4 20 | with: 21 | python-version: ${{ matrix.python }} 22 | - name: Install dependencies 23 | run: python -m pip install tox ruff 24 | - name: Run ruff 25 | run: ruff check . 26 | - name: Run ruff format 27 | run: ruff format --check . 28 | - name: Run unit tests (via tox) 29 | # Run tox using the version of Python in `PATH` 30 | run: tox -e py 31 | docs: 32 | name: Build docs 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Checkout source code 36 | uses: actions/checkout@v3 37 | with: 38 | fetch-depth: 0 39 | - name: Set up Python 3.11 40 | uses: actions/setup-python@v4 41 | with: 42 | python-version: "3.11" 43 | - name: Install dependencies 44 | run: python -m pip install docutils 45 | - name: Build docs 46 | run: rst2html.py README.rst README.html 47 | if: matrix.python == '3.8' 48 | - name: Build docs 49 | run: rst2html README.rst README.html 50 | if: matrix.python != '3.8' 51 | - name: Archive build results 52 | uses: actions/upload-artifact@v4.6.0 53 | with: 54 | name: html-docs-build 55 | path: README.html 56 | retention-days: 7 57 | release: 58 | name: Upload release artifacts 59 | runs-on: ubuntu-latest 60 | needs: test 61 | if: github.event_name == 'push' 62 | steps: 63 | - name: Checkout source code 64 | uses: actions/checkout@v3 65 | with: 66 | fetch-depth: 0 67 | - name: Set up Python 3.11 68 | uses: actions/setup-python@v4 69 | with: 70 | python-version: "3.11" 71 | - name: Install dependencies 72 | run: python -m pip install build 73 | - name: Build a binary wheel and a source tarball 74 | run: python -m build --sdist --wheel --outdir dist/ . 75 | - name: Publish distribution to PyPI 76 | if: startsWith(github.ref, 'refs/tags') 77 | uses: pypa/gh-action-pypi-publish@release/v1 78 | with: 79 | password: ${{ secrets.PYPI_API_TOKEN }} 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | TAGS 2 | tags 3 | lib/testtools 4 | MANIFEST 5 | dist 6 | .testrepository 7 | __pycache__ 8 | fixtures.egg-info 9 | *.pyc 10 | .*.swp 11 | *~ 12 | .eggs 13 | build 14 | .tox 15 | fixtures/_version.py 16 | -------------------------------------------------------------------------------- /.testr.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | test_command=PYTHONPATH=lib ${PYTHON:-python} -m subunit.run $LISTOPT $IDOPTION fixtures.test_suite 3 | test_id_option=--load-list $IDFILE 4 | test_list_option=--list 5 | 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Andrew Laski 2 | Balazs Gibizer 3 | Brant Knudson 4 | Clark Boylan 5 | Dan Kenigsberg 6 | Edward Betts 7 | Francesco Banconi 8 | Free Ekanayaka 9 | Gabi Davar 10 | Gavin Panella 11 | Hugo 12 | James Westby 13 | Jelmer Vernooij 14 | John L. Villalovos 15 | Jonathan Lange 16 | Joshua Harlow 17 | Julien Danjou 18 | Jürgen Gmach 19 | Martin Pool 20 | Robert Collins 21 | Sean Dague 22 | Stephen Finucane 23 | Steve Kowalik 24 | hugovk 25 | -------------------------------------------------------------------------------- /Apache-2.0: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /BSD: -------------------------------------------------------------------------------- 1 | Copyright (c) Robert Collins and Fixtures contributors 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. Neither the name of Robert Collins nor the names of Subunit contributors 13 | may be used to endorse or promote products derived from this software 14 | without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY ROBERT COLLINS AND SUBUNIT CONTRIBUTORS ``AS IS'' 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 | SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Fixtures is licensed under two licenses, the Apache License, Version 2.0 2 | or the 3-clause BSD License. You may use this project under either of these 3 | licenses - choose the one that works best for you. 4 | 5 | We require contributions to be licensed under both licenses. The primary 6 | difference between them is that the Apache license takes care of potential 7 | issues with Patents and other intellectual property concerns that some users 8 | or contributors may find important. 9 | 10 | Generally every source file in Fixtures needs a license grant under both 11 | these licenses. As the code is shipped as a single unit, a brief form is used: 12 | ---- 13 | Copyright (c) [yyyy][,yyyy]* [name or 'Fixtures Contributors'] 14 | 15 | Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 16 | license at the users choice. A copy of both licenses are available in the 17 | project source as Apache-2.0 and BSD. You may not use this file except in 18 | compliance with one of these two licences. 19 | 20 | Unless required by applicable law or agreed to in writing, software 21 | distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 22 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 23 | license you chose for the specific language governing permissions and 24 | limitations under that license. 25 | ---- 26 | 27 | Code that has been incorporated into Fixtures from other projects will 28 | naturally be under its own license, and will retain that license. 29 | 30 | A known list of such code is maintained here: 31 | * No entries. 32 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | CHANGES 2 | ======= 3 | 4 | 4.2.5 5 | ----- 6 | * Add missing Apache license to sdist. 7 | 8 | 4.2.3.post2 9 | ----------- 10 | * Add missing license and metadata files to sdist. 11 | 12 | 4.2.3.post1 13 | ----------- 14 | * Remove accidentally included `venv` folder. 15 | 16 | 4.2.3 17 | ----- 18 | * Remove dependency of pbr and start using pyproject.toml for setuptools 19 | 20 | 3.0.0 - 4.2.2 21 | ------------- 22 | * Remove six and other Python 2 handling code 23 | * Drop support for Python < 3.6, add 3.7 - 3.9 24 | * Fix tests on Python 3.9 25 | * Remove use of 'extras' 26 | * Drop support for EOL Python 2.6 and 3.3 27 | * Add possibility to reset the FakeLogger 28 | * Drop support for EOL Python 3.3 29 | * Drop support for EOL Python 2.6 30 | * pick 8626952e Test ImageDraw2 Use latest PyPy3 31 | * Remove unused imports and variables 32 | * Drop support for EOL Python 2.6 33 | * Correct spelling mistakes 34 | * Update classifiers 35 | * Add python 3.6 support (#36) 36 | * Add missing APIs to FakeProcess, making it match Popen (LP #1373224) 37 | * and many more which have not been properly tracked 38 | 39 | 3.0.0 40 | ----- 41 | 42 | * Release 3.0.0 43 | * Fixup the MonkeyPatch patch 44 | * Tweak the new tests for consistency 45 | * Update the semantics on \_fixtures.MonkeyPatch 46 | * Avoid old versions of pbr - we require modern releases 47 | * Correct MockPatchMultiple example 48 | * Ignore .tox 49 | 50 | 2.0.0 51 | ----- 52 | 53 | * Fixup NEWS, release 2.0 54 | * MonkeyPatch staticmethod 55 | * Drop support for Python 3.2. It's history 56 | * Fix print in README 57 | * Add CompoundFixture 58 | * Tweak hacking docs 59 | * Fix "propagate" spelling everywhere 60 | * Missed one: propogate -> propagate 61 | * Spelling and lint fixes 62 | 63 | 1.4 64 | --- 65 | 66 | * Release 1.4 67 | * Trivial pep8 fix to logger.py 68 | * FakeLogger: Mis-formatted log messages will raise Exception 69 | * Use mock in preference to unittest.mock 70 | * Add a .travis.yml 71 | * Note how to push on releases 72 | 73 | 1.3.1 74 | ----- 75 | 76 | * Release 1.3.1 77 | * Clarify the intent around \_setUp 78 | * Handle BaseException resource leaks as well 79 | 80 | 1.3.0 81 | ----- 82 | 83 | * Release 1.3.0 84 | * Remove trailing whitespace 85 | * Deal with resource leaks during setUp 86 | * Missed NEWS entry 87 | * Fine tune the mock patch 88 | * Add a new mockpatch fixture 89 | * Document where the project home and source are 90 | * Ignore built things 91 | 92 | 1.2.0 93 | ----- 94 | 95 | * Release 1.2.0 96 | * Add a warnings module capture fixure 97 | * Use universal wheels 98 | 99 | 1.1.0 100 | ----- 101 | 102 | * Release 1.1.0 and use pbr 0.11 features 103 | * Missing NEWS entry 104 | * add tox.ini file 105 | * Fixed test performance on Python 3.5 106 | * Add NEWS for FakeLogger formatter 107 | * allow the specification of a custom log formatter 108 | 109 | 1.0.0 110 | ----- 111 | 112 | * Release 1.0.0 113 | * remote copy/paste from another project 114 | 115 | 0.3.17 116 | ------ 117 | 118 | * Release 0.3.17 119 | * Add support for datefmt in FakeLogger 120 | * Migrate to git and pbr 121 | 122 | 0.3.16 123 | ------ 124 | 125 | * 0.3.16 ~~~~~~ 126 | 127 | 0.3.15 128 | ------ 129 | 130 | * Release 0.3.15 131 | * \* \`\`FakeProcess\`\` wait() now supports arguments added in Python 3. (Steve Kowalik) 132 | * \* \`\`FakeProcess\`\` wait() now supports arguments added in Python 3. (Steve Kowalik) 133 | * \* \`\`FakeProcess\`\` now supports kill(). (Steve Kowalik) 134 | * \* \`\`FakePopen\`\` now supports being called under a context manager (IE: with). (Steve Kowalik) 135 | * \* \`\`MonkeyPatch\`\` now preserves \`\`staticmethod\`\` functions. (Dan Kenigsberg) 136 | * \* \`\`FakeProcess\`\` now supports kill(). (Steve Kowalik) 137 | * \* \`\`FakePopen\`\` now works under a context manager itself. (Steve Kowalik, #1358085) 138 | * MonkeyPatch staticmethod 139 | 140 | 0.3.14 141 | ------ 142 | 143 | * Release 0.3.14 144 | * \* \`\`FakePopen\`\` can now override the returncode attribute. (Robert Collins) 145 | * More releasing docs 146 | 147 | 0.3.13 148 | ------ 149 | 150 | * Release 0.3.13 151 | * \* Documentation hopefully covers \`\`TestWithFixtures\`\` a little better. (Robert Collins, #1102688) 152 | * Ignore an egg-info directory if it exists 153 | * \* \`\`setup.py\`\` now lists the \`\`testtools\`\` dependency which was missing. (Robert Collins, #1103823) 154 | * \* \`\`FakePopen\`\` now accepts all the parameters available in Python 2.7. (Robert Collins) 155 | 156 | 0.3.12 157 | ------ 158 | 159 | * 0.3.12: 0.3.11 with tests for StringStream 160 | * Oops, setup.py wasn't 3.2 ready 161 | * Add Python 3 Trove entry 162 | 163 | 0.3.11 164 | ------ 165 | 166 | * Release 0.3.11 167 | * \* pydoc is recommended as a source of info about fixtures. (Robert Collins, #812845) 168 | * \* The docs for fixtures have been updated to cover the full API. (Robert Collins, #1071649) 169 | * \* \`\`DetailStream\`\` was ambiguous about whether it handled bytes or characters, which matters a lot for Python3. It now is deprecated with ByteStream and StringStream replacing it. (Robert Collins) 170 | * Update docs 171 | * \* \`\`DetailStream\`\` was ambiguous about whether it handled bytes or characters, which matters a lot for Python3. It now is deprecated with ByteStream and StringStream replacing it. (Robert Collins) 172 | * \* \`\`FakeLogger\`\` has been split out into a \`\`LogHandler\`\` fixture that can inject arbitrary handlers, giving more flexability. (Jonathan Lange) 173 | * Drop the MementoHandler stuff 174 | * Rest of the tests for LogHandler 175 | * Give up on MementoHandler, just test LogHandler instead 176 | * Change the MementoHandler to store a dict. Start testing the logger fixture 177 | * Make handler public 178 | * Extract the handler managing bit of FakeLogger into its own fixture 179 | * Add MementoHandler 180 | * Release 0.3.10 181 | * \* New \`\`DetailStream\`\` fixture to add file-like object content to testtools details. This allows for easy capture of sys.stdout and sys.stderr for example. (Clark Boylan) 182 | * Document DetailStream 183 | * \* New \`\`DetailStream\`\` fixture to add file-like object content to testtools details. This allows for easy capture of sys.stdout and sys.stderr for example. (Clark Boylan) 184 | * \* Factor out new \`\`CallMany\`\` class to isolate the cleanup logic. (Robert Collins) 185 | * Add 'join' method to TempDir 186 | * Revert filetree patch, r54. Now in lp:treeshape 187 | * Rename to 'join' 188 | * Update NEWS 189 | * Add an 'abspath' helper 190 | * Roll back properly 191 | * Remove filetree cruft 192 | * Add facility to make a tree of files on a TempDir (r=lifeless) 193 | * NEWS 194 | * Change the API to have \*args 195 | * Move filetree tests to be against tempdir 196 | * Remove duplicate tests 197 | * Remove filetree 198 | * Remove FileTree 199 | * Make FileTree a thing on tempdir 200 | * Rename NoHasattr to HasNoAttribute 201 | * Spell it more simply 202 | * Heaps more docstrings 203 | * Integration test 204 | * Create parent directories 205 | * Bump the testtools dependency 206 | * Do stuff as late as possible 207 | * Extract out another function 208 | * Richer error messages 209 | * Extract the bit that normalizes the entries 210 | * Extract normalize\_shape 211 | * Refactoring. Some comments in the tests 212 | * Docs. Nicer directory specification 213 | * Basic directory creation 214 | * Start writing stuff to disk 215 | * Initial creation of FileTree 216 | * Add a matcher, because I can't bear to write that code again 217 | * Clean up pyflakes 218 | * Export TempHomeDir. Otherwise it's imported here for no reason 219 | 220 | 0.3.9 221 | ----- 222 | 223 | * Release 0.3.9 224 | * Include logging output in FakeLoggers' getDetails, surfacing the logs to testtools tests automatically 225 | * Removed unused text\_content import 226 | * Updated the way the detail name is added 227 | * Fixes from review 228 | * Implemented FakeLogger.getDetails() 229 | * New TempHomeDir fixture to create and activate a temporary home dir (james\_w) 230 | * Subclass TempDir rather than composing 231 | * Fix the typo. Thanks Rob 232 | * Remove the debugging echo 233 | * Add a TempHomeDir fixture 234 | 235 | 0.3.8 236 | ----- 237 | 238 | * Release 0.3.8 239 | * MNerge NestedTempfile - make tempfile default to a new default location 240 | * Rationalise fixture names. (Jonathan Lange) 241 | * Fix race conditions in Timeout 242 | * Update to parallel capable .testr.conf 243 | * Reverse order of operands to assertNotEqual to follow conventions 244 | * New fixture NestedTempfile 245 | * Rename to just TimeoutException, and remove more connections to Timeout only being used in tests 246 | * Rename to just 'Timeout'; other review cleanups 247 | * Add TestTimeout fixture 248 | * Correctly disambiguate duplicate errors 249 | * Copyright 250 | * PopenFixture => FakePopen 251 | * Rename LoggerFixture to FakeLogger 252 | * Rename EnvironmentVariableFixture to EnvironmentVariable 253 | * Another typo 254 | * EnvironmentVariableFixture now upcalls via super 255 | * Add docs for LoggerFixture to README 256 | * Fix typo 257 | * Open up 0.3.8 258 | * Release 0.3.7 259 | * Add new LoggingFixture fixture for replacing logging Loggers 260 | * Upcall w/ super() 261 | * Nuke handlers by default 262 | * Add LoggerFixture 263 | * Upcall. Doh 264 | * Bump version to 0.3.7 beta 265 | * Typo 266 | * Extend TempDir to allow controlling the root directory 267 | * \* On Python 2.7 and above the \_fixtures tests are no longer run twice. (Robert Collins) 268 | * Note in NEWS the new testtools version dependency as well 269 | * NEWS & README 270 | * Pretty sure this is a py3 thing 271 | * Make the tests run with python 2 and 3 272 | * Update to take testtools new API into account 273 | * Release 0.3.6 274 | * Another small API break - sorry. Fixture.getDetails no longer returns the internal details dict (self.\_details). Access that directly if needed. It now looks for details in used fixtures and returns those as well as details added directly. (RobertCollins, #780806) 275 | * New fixture \`\`PackagePathEntry\`\` which patches the path of an existing package, allowing importing part of it from aonther directory. (Robert Collins) 276 | * \* Details from child fixtures for both Fixture and TestWithFixtures no longer quash same-named details if testtools 0.9.11 is available (for the gather\_details helper). (Gavin Panella, #796253) 277 | * Test failure on some setups in test\_cleanUp\_raise\_first\_false\_callscleanups\_returns\_exceptions. (Gavin Panella, #796249) 278 | * Gather details from fixtures that fail to setUp() in TestWithFixtures 279 | * Use testtools.helpers.try\_import instead of try:except: 280 | * Skip test\_useFixture\_details\_captured\_from\_setUp if gather\_details is not available 281 | * Reminder to self to skip test if gather\_details is not available 282 | * Test against types.TracebackType instead of the type of the current traceback in sys.exc\_info() 283 | * Gather details from fixtures that fail to setUp() 284 | * \* New fixture \`\`PythonPathEntry\`\` which patches sys.path. (Robert Collins, #737503) 285 | * Better docs for cleanUp and exceptions 286 | * Document sharing dependencies somewhat 287 | * Release 0.3.5 288 | * New fixture \`\`PythonPackage\`\` which manages a temporary python package. (Robert Collins) 289 | * Add a TempDir fixture 290 | * More docs 291 | * New helper \`\`MonkeyPatch\`\` which patches (or deletes) an existing attribute and restores it afterwards. (Robert Collins) 292 | * Release 0.3.4 293 | * Fixture now supports \`\`addDetail\`\` and provides a\`\`getDetails\`\` call compatible with the \`\`testtools.TestCase\`\` calls. (Robert Collins, #640119) 294 | * Add MethodFixture for easier wrapping of existing fixture-like objects 295 | * Fixtures now have a \`\`useFixture\`\` method as well, making nesting of fixtures trivial 296 | * Add EnvironmentVariableFixture 297 | * Ship new packages 298 | * Release 0.3.1 299 | * Add a communicate method to FakeProcess 300 | * \* Experimental PopenFixture providing a test double for testing code that runs external processes. (Robert Collins) 301 | * Fixup cleanUp protocol for good 302 | * Stop silently swallowing exceptions during cleanUp 303 | * Cause cleanup failures to cause test failures 304 | * Correct the example context manager in README, and provide a protocol for cleanUp to signal exceptions 305 | * Document a shortcoming of using fixtures as context managers 306 | * First draft - 0.1 307 | -------------------------------------------------------------------------------- /GOALS: -------------------------------------------------------------------------------- 1 | fixtures goals 2 | ============== 3 | 4 | * Declarative interface for creating both simple and complex fixtures for 5 | tests. 6 | 7 | * Play nice with testscenarios (for parameterisation of tests) and 8 | testresources (for optimising the use and overhead of expensive fixtures. 9 | 10 | * Either define a protocol for decoration-to-inject-fixtures, or explicitly 11 | delegate that to testresources/testscenarios (but require that if delegated 12 | the simple case should still be simple). 13 | 14 | * Be able to, in princple, provide test isolation like chdir and $HOME 15 | substition so that tests which work with external processes aren't tainted by 16 | the users home dir. 17 | 18 | * Be able to manage external processes as a fixture. 19 | 20 | * usable in trial, bzr, Zope testrunner, nose and the plain python unittest 21 | module. 22 | 23 | * Be useful outside of a purely testing context. 24 | -------------------------------------------------------------------------------- /HACKING: -------------------------------------------------------------------------------- 1 | Contributing to fixtures 2 | ======================== 3 | 4 | Code access 5 | +++++++++++ 6 | 7 | Branch from the trunk (all patches should be for trunk unless there are 8 | exceptional circumstances):: 9 | 10 | git clone https://github.com/testing-cabal/fixtures path-to-new-local-repo 11 | 12 | Publish your branches wherever you like and submit PR's on github. 13 | 14 | Copyright 15 | +++++++++ 16 | 17 | Fixtures is Copyright (C) 2010-2014 Robert Collins. I'd like to be able to 18 | offer it up for stdlib inclusion once it has proved itself, so am asking for 19 | copyright assignment to me - or for your contributions to be under the BSD and 20 | Apache-2.0 licences that Fixtures is under (which permit inclusion in Python). 21 | 22 | Coding standards 23 | ++++++++++++++++ 24 | 25 | PEP-8 coding style please, though perfection isn't needed. Make sure that 'make 26 | check' passes before sending in a patch. 27 | 28 | Code arrangement 29 | ++++++++++++++++ 30 | 31 | The ``fixtures`` module should simply import classes and functions from 32 | more specific modules, rather than becoming large and bloated itself. For 33 | instance, TestWithFixtures lives in fixtures.testcase, and is imported in 34 | the fixtures __init__.py. 35 | 36 | Releasing 37 | +++++++++ 38 | 39 | 1. Add a version to NEWS. 40 | 41 | 1. commit, tag:: 42 | 43 | git commit -am "Release X.Y.Z" 44 | git tag -m "Release X.Y.Z" X.Y.Z 45 | git push --tags origin master:master 46 | 47 | 1. Upload to pypi, signed. 48 | 49 | 1. Close bugs. 50 | 51 | 1. Rename the next milestone, release it, and make a new one. 52 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PYTHON ?= python3 2 | 3 | all: check 4 | 5 | check: 6 | $(PYTHON) -m testtools.run fixtures.test_suite 7 | 8 | clean: 9 | find . -name '*.pyc' -print0 | xargs -0 rm -f 10 | 11 | TAGS: fixtures/*.py fixtures/tests/*.py 12 | ctags -e -R fixtures/ 13 | 14 | tags: fixtures/*.py fixtures/tests/*.py 15 | ctags -R fixtures/ 16 | 17 | .PHONY: all check clean 18 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | ---------------------- 2 | fixtures release notes 3 | ---------------------- 4 | 5 | NEXT 6 | ~~~~ 7 | 8 | 4.2.2 9 | ~~~~~ 10 | 11 | * Republish using Trusted Publishing (properly this time). 12 | (Colin Watson) 13 | 14 | 4.2.1 15 | ~~~~~ 16 | 17 | * Republish using Trusted Publishing. 18 | (Colin Watson) 19 | 20 | 4.2.0 21 | ~~~~~ 22 | 23 | * Use ``code-block`` directive for Python code. 24 | (Pavel Kulyov) 25 | 26 | * Add support for Python 3.12 and 3.13. 27 | (James Page, Colin Watson) 28 | 29 | * Drop support for Python 3.7 (EOL) 30 | (Jelmer Vernooij) 31 | 32 | * Drop support for external ``mock`` package. 33 | (Colin Watson, https://github.com/testing-cabal/fixtures/issues/88) 34 | 35 | 4.1.0 36 | ~~~~~ 37 | 38 | * Drop support for Python 3.6 (EOL) 39 | (Stephen Finucane) 40 | 41 | * Add a new ``WarningsFilter`` filter, allowing users to filter warnings as 42 | part of their tests, before restoring said filters. 43 | (Stephen Finucane) 44 | 45 | 4.0.1 46 | ~~~~~ 47 | 48 | * Remove ``testtools`` from ``requirements.txt`` as well. 49 | (Colin Watson) 50 | 51 | 4.0.0 52 | ~~~~~ 53 | 54 | * Add missing APIs to ``FakeProcess``, making it match ``Popen``. 55 | (Free Ekanayaka, #1373224) 56 | 57 | * Dropped support for Python 2.7, Python 3.4 and Python 3.5 (EOL). 58 | (Hugo van Kemenade) 59 | 60 | * Added support for Python 3.6-3.10. 61 | (Free Ekanayaka, Stephen Finucane, Colin Watson) 62 | 63 | * Add possibility to reset the ``FakeLogger``. (Balazs Gibizer) 64 | 65 | * Access ``mock.DEFAULT`` lazily rather than at import time so ``mock`` can 66 | be overridden with something else. (Jelmer Vernooij) 67 | 68 | * Support all ``subprocess.Popen`` arguments up to Python 3.10. 69 | (Jürgen Gmach, Colin Watson) 70 | 71 | * Move ``testtools`` requirement to a new ``fixtures[streams]`` extra. 72 | (Colin Watson) 73 | 74 | 3.0.0 75 | ~~~~~ 76 | 77 | * Monkeypatching of callables has been thoroughly overhauled: there were deep 78 | flaws in usability because staticmethod and classmethods are actually 79 | descriptors. (Andrew Laski, Robert Collins) 80 | 81 | 2.0 82 | ~~~ 83 | 84 | * Monkeypatching of staticmethod's now works correctly on Python 2 and 3. 85 | This is an API break as previously they were bound as instancemethods's 86 | and thus any working patches need to have their first parameter (self) 87 | dropped. (Andrew Laski) 88 | 89 | * New class ``CompoundFixture`` added, allowing fixtures to be combined. 90 | (Jonathan Lange) 91 | 92 | * Support for Python 3.2 dropped (as pip and setuptools have). 93 | (Robert Collins) 94 | 95 | 1.4 96 | ~~~ 97 | 98 | * ``fixtures`` now has CI testing via Travis, no more manual work. 99 | (Robert Collins) 100 | 101 | * ``mock`` is used in preference to ``unittest.mock`` now, as the rolling 102 | backport has significant bugfixes over older but still supported CPython 103 | stdlib versions. (Robert Collins) 104 | 105 | * ``fixtures.FakeLogger`` now detects incorrectly formatted log messages. 106 | (John Villalovos, #1503049) 107 | 108 | 1.3.1 109 | ~~~~~ 110 | 111 | * ``Fixture.setUp`` now uses a bare except: and will thus catch BaseException. 112 | Any non-Exception-subclass errors are raised verbatim after calling 113 | ``cleanUp``, to avoid inappropriate masking in callers. (Robert Collins) 114 | 115 | 1.3.0 116 | ~~~~~ 117 | 118 | * Add MockPatch, MockPatchMultiple, MockPatchObject - adapters to mock. 119 | (Julien Danjou, Robert Collins) 120 | 121 | * Fixture.setUp should no longer be overridden in subclasses. Instead 122 | override _setUp. This permits the Fixture base class to detect failures 123 | during _setUp and trigger any registered cleanups, attach any details 124 | to the failure exception and propagate that to callers. Overriding of 125 | setUp is still supported: this adds a new interface for simpler 126 | implementation of the contract of a fixture. 127 | (Robert Collins, #1456361, #1456353) 128 | 129 | 1.2.0 130 | ~~~~~ 131 | 132 | * Add warnings module fixture for capturing warnings. (Joshua Harlow) 133 | 134 | 1.1.0 135 | ~~~~~ 136 | 137 | CHANGES 138 | ------- 139 | 140 | * FakeLogger now supports a custom formatter. (Sean Dague) 141 | 142 | * Fixed test performance on Python 3.5. PEP 475 led to ``time.sleep()`` not 143 | being interrupted when a received signal handler eats the signal (rather 144 | than raising an exception). (Robert Collins) 145 | 146 | * ``tox.ini`` added, for folk that use tox. (Sean Dague) 147 | 148 | 1.0.0 149 | ~~~~~ 150 | 151 | CHANGES 152 | ------- 153 | 154 | * Fix incorrect entry_point. (Gabi Davar) 155 | 156 | 0.3.17 157 | ~~~~~~ 158 | 159 | CHANGES 160 | ------- 161 | 162 | * FakeLogger now supports the ``datefmt`` parameter. 163 | (Sean Dague) 164 | 165 | * Fixtures source code is now hosted on 166 | `github `_. 167 | (Robert Collins) 168 | 169 | 0.3.16 170 | ~~~~~~ 171 | 172 | CHANGES 173 | ------- 174 | 175 | * Fixed 0.3.15 on Python 2.6 - version info is a plain tuple there. 176 | (Robert Collins) 177 | 178 | 0.3.15 179 | ~~~~~~ 180 | 181 | CHANGES 182 | ------- 183 | 184 | * ``FakePopen`` now supports being called under a context manager (IE: with). 185 | (Steve Kowalik) 186 | 187 | * ``FakeProcess`` now supports kill(). (Steve Kowalik) 188 | 189 | * ``FakeProcess`` wait() now supports arguments added in Python 3. 190 | (Steve Kowalik) 191 | 192 | * ``MonkeyPatch`` now preserves ``staticmethod`` functions. 193 | (Dan Kenigsberg) 194 | 195 | 0.3.14 196 | ~~~~~~ 197 | 198 | CHANGES 199 | ------- 200 | 201 | * ``FakePopen`` can now override the returncode attribute. 202 | (Robert Collins) 203 | 204 | 0.3.13 205 | ~~~~~~ 206 | 207 | CHANGES 208 | ------- 209 | 210 | * Documentation hopefully covers ``TestWithFixtures`` a little better. 211 | (Robert Collins, #1102688) 212 | 213 | * ``FakePopen`` now accepts all the parameters available in Python 2.7. 214 | (Robert Collins) 215 | 216 | * ``FakePopen`` now only passes parameters to the get_info routine if the 217 | caller supplied them. (Robert Collins) 218 | 219 | * ``setup.py`` now lists the ``testtools`` dependency which was missing. 220 | (Robert Collins, #1103823) 221 | 222 | 0.3.12 223 | ~~~~~~ 224 | 225 | Brown bag fix up of StringStream from 0.3.11. 226 | 227 | 0.3.11 228 | ~~~~~~ 229 | 230 | CHANGES 231 | ------- 232 | 233 | * ``DetailStream`` was ambiguous about whether it handled bytes or characters, 234 | which matters a lot for Python3. It now is deprecated with ByteStream and 235 | StringStream replacing it. (Robert Collins) 236 | 237 | * Fixtures is now Python3 compatible. (Robert Collins) 238 | 239 | * ``FakeLogger`` has been split out into a ``LogHandler`` fixture that can 240 | inject arbitrary handlers, giving more flexability. (Jonathan Lange) 241 | 242 | * pydoc is recommended as a source of info about fixtures. 243 | (Robert Collins, #812845) 244 | 245 | * The docs for fixtures have been updated to cover the full API. 246 | (Robert Collins, #1071649) 247 | 248 | 0.3.10 249 | ~~~~~~ 250 | 251 | New ``DetailStream`` fixture to add file-like object content to testtools 252 | details, cleanup logic factored out into a CallMany class. 253 | 254 | CHANGES: 255 | 256 | * Add ``join`` method to ``TempDir`` to more readily get paths relative 257 | to a temporary directory. (Jonathan Lange) 258 | 259 | * Factor out new ``CallMany`` class to isolate the cleanup logic. 260 | (Robert Collins) 261 | 262 | * New ``DetailStream`` fixture to add file-like object content to testtools 263 | details. This allows for easy capture of sys.stdout and sys.stderr for 264 | example. (Clark Boylan) 265 | 266 | 0.3.9 267 | ~~~~~ 268 | 269 | New ``TempHomeDir`` fixture and more log output included in ``FakeLogger``. 270 | 271 | CHANGES: 272 | 273 | * New TempHomeDir fixture to create and activate a temporary home directory. 274 | (James Westby) 275 | 276 | * ``FakeLogger`` now includes the output from python logging - the .output 277 | attribute as a content object in getDetails, so the logs will automatically 278 | be included in failed test output (or other similar circumstances). 279 | (Francesco Banconi) 280 | 281 | 0.3.8 282 | ~~~~~ 283 | 284 | Simpler names for a number of fixtures, and two new fixtures NestedTempfile and 285 | Timeout. See the manual for more information. 286 | 287 | CHANGES: 288 | 289 | * EnvironmentVariable now upcalls via super(). 290 | (Jonathan Lange, #881120) 291 | 292 | * EnvironmentVariableFixture, LoggerFixture, PopenFixture renamed to 293 | EnvironmentVariable, FakeLogger and FakePopen respectively. All are still 294 | available under their old, deprecated names. (Jonathan Lange, #893539) 295 | 296 | * gather_details fixed to handle the case where two child fixtures have the 297 | same detail name. (Jonathan Lange, #895652) 298 | 299 | * ``NestedTempfile`` which will change the default path for tempfile temporary 300 | file / directory creation. (Gavin Panella) 301 | 302 | * New Timeout fixture. (Martin Pool) 303 | 304 | 0.3.7 305 | ~~~~~ 306 | 307 | CHANGES: 308 | 309 | * New fixture LoggerFixture which replaces a logging module logger. 310 | (Free Ekanayaka) 311 | 312 | * On Python 2.7 and above the _fixtures tests are no longer run twice. 313 | (Robert Collins) 314 | 315 | * Python 3 now supported. (Jonathan Lange, #816665) 316 | 317 | * ``TempDir`` supports creating the temporary directory under a specific path. 318 | An example of where this is useful would be if you have some specific long 319 | lived temporary items and need to avoid running afoul of tmpreaper. 320 | (Robert Collins, #830757) 321 | 322 | * Updated to match the changed ``gather_details`` API in testtools. See #801027. 323 | This is an incompatible change in testtools and requires testtools 0.9.12. 324 | (Jonathan Lange) 325 | 326 | 0.3.6 327 | ~~~~~ 328 | 329 | Another small API break - sorry. Fixture.getDetails no longer returns the 330 | internal details dict (self._details). Access that directly if needed. It now 331 | looks for details in used fixtures and returns those as well as details added 332 | directly. 333 | 334 | CHANGES: 335 | 336 | * Details from child fixtures for both Fixture and TestWithFixtures no longer 337 | quash same-named details if testtools 0.9.11 is available (for the 338 | gather_details helper). 339 | (Gavin Panella, #796253) 340 | 341 | * Fixture.getDetails will now return all the details of fixtures added using 342 | useFixture. (RobertCollins, #780806) 343 | 344 | * New fixture ``PackagePathEntry`` which patches the path of an existing 345 | package, allowing importing part of it from another directory. 346 | (Robert Collins) 347 | 348 | * New fixture ``PythonPathEntry`` which patches sys.path. 349 | (Robert Collins, #737503) 350 | 351 | * Test failure on some setups in 352 | test_cleanUp_raise_first_false_callscleanups_returns_exceptions. 353 | (Gavin Panella, #796249) 354 | 355 | * Testtools 0.9.8 is now a minimum requirement. 356 | (Gavin Panella) 357 | 358 | 0.3.5 359 | ~~~~~ 360 | 361 | CHANGES: 362 | 363 | * New fixture ``MonkeyPatch`` which patches (or deletes) an existing attribute 364 | and restores it afterwards. (Robert Collins) 365 | 366 | * New fixture ``PythonPackage`` which manages a temporary python package. 367 | (Robert Collins) 368 | 369 | * New fixture ``TempDir`` which manages a temporary directory. (Robert Collins) 370 | 371 | 0.3.4 372 | ~~~~~ 373 | 374 | CHANGES: 375 | 376 | * Fixture now supports ``addDetail`` and provides a``getDetails`` call 377 | compatible with the ``testtools.TestCase`` calls. (Robert Collins, #640119) 378 | 379 | * New helper ``MethodFixture`` for wrapping an object similarly to 380 | ``FunctionFixture`` (Robert Collins) 381 | 382 | 0.3.3 383 | ~~~~~ 384 | 385 | Fixtures now have a ``useFixture`` method as well, making nesting of fixtures 386 | trivial. 387 | 388 | CHANGES: 389 | 390 | * New method ``Fixture.useFixture`` allowing fixtures to compose other 391 | fixtures. (Robert Collins) 392 | 393 | 0.3.2 394 | ~~~~~ 395 | 396 | Point release adding new EnvironmentVariableFixture for controlling environment 397 | variables. 398 | 399 | CHANGES: 400 | 401 | * New EnvironmentVariableFixture which can set or unset environment variables. 402 | (Robert Collins) 403 | 404 | 0.3.1 405 | ~~~~~ 406 | 407 | Point release adding experimental PopenFixture. 408 | 409 | CHANGES: 410 | 411 | * Experimental PopenFixture providing a test double for testing code that runs 412 | external processes. (Robert Collins) 413 | 414 | 0.3 415 | ~~~ 416 | 417 | This release slightly breaks compatibility in order to get the cleanUp API 418 | really solid : it now will report correctly with testtools 0.9.7, and 419 | generally does the right thing by default. Apologies to anyone affected by 420 | this churn, but I felt the cleanness of the API was worth it. 421 | 422 | CHANGES: 423 | 424 | * When multiple exceptions are being raised from cleanUp, MultipleExceptions 425 | is used to report them all. This is preferentially imported from testtools 426 | and failing that defined locally. See testtools 0.9.7 for its use. 427 | (Robert Collins) 428 | 429 | 0.2 430 | ~~~ 431 | 432 | CHANGES: 433 | 434 | * Exceptions raised during cleanup are no longer silently swallowed. They 435 | are returned in a list by default, or the first one is raised if raise_first 436 | is supplied. The TestCase glue code passes raise_first to cleanUp. 437 | (Robert Collins, #629032) 438 | 439 | 0.1 440 | ~~~ 441 | 442 | CHANGES: 443 | 444 | * Created project. The primary interfaces are 445 | ``fixtures.TestWithFixtures`` and ``fixtures.Fixture``. 446 | Documentation is primarily in README. (Robert Collins) 447 | 448 | -------------------------------------------------------------------------------- /fixtures/__init__.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, 2011, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | 17 | """Fixtures provides a sensible contract for reusable test fixtures. 18 | 19 | It also provides glue for using these in common test runners and acts as a 20 | common repository for widely used Fixture classes. 21 | 22 | See the README for a manual, and the docstrings on individual functions and 23 | methods for details. 24 | 25 | Most users will want to look at TestWithFixtures and Fixture, to start with. 26 | """ 27 | 28 | from fixtures._version import __version__ 29 | 30 | __all__ = [ 31 | "ByteStream", 32 | "CompoundFixture", 33 | "DetailStream", 34 | "EnvironmentVariable", 35 | "EnvironmentVariableFixture", 36 | "FakeLogger", 37 | "FakePopen", 38 | "Fixture", 39 | "FunctionFixture", 40 | "LogHandler", 41 | "LoggerFixture", 42 | "MethodFixture", 43 | "MockPatch", 44 | "MockPatchMultiple", 45 | "MockPatchObject", 46 | "MonkeyPatch", 47 | "MultipleExceptions", 48 | "NestedTempfile", 49 | "PackagePathEntry", 50 | "PopenFixture", 51 | "PythonPackage", 52 | "PythonPathEntry", 53 | "SetupError", 54 | "StringStream", 55 | "TempDir", 56 | "TempHomeDir", 57 | "TestWithFixtures", 58 | "Timeout", 59 | "TimeoutException", 60 | "WarningsCapture", 61 | "WarningsFilter", 62 | "__version__", 63 | ] 64 | 65 | 66 | from fixtures.fixture import ( # noqa: E402 67 | CompoundFixture, 68 | Fixture, 69 | FunctionFixture, 70 | MethodFixture, 71 | MultipleExceptions, 72 | SetupError, 73 | ) 74 | from fixtures._fixtures import ( # noqa: E402 75 | ByteStream, 76 | DetailStream, 77 | EnvironmentVariable, 78 | EnvironmentVariableFixture, 79 | FakeLogger, 80 | FakePopen, 81 | LoggerFixture, 82 | LogHandler, 83 | MockPatch, 84 | MockPatchMultiple, 85 | MockPatchObject, 86 | MonkeyPatch, 87 | NestedTempfile, 88 | PackagePathEntry, 89 | PopenFixture, 90 | PythonPackage, 91 | PythonPathEntry, 92 | StringStream, 93 | TempDir, 94 | TempHomeDir, 95 | Timeout, 96 | TimeoutException, 97 | WarningsCapture, 98 | WarningsFilter, 99 | ) 100 | from fixtures.testcase import TestWithFixtures # noqa: E402 101 | 102 | 103 | def test_suite(): 104 | import fixtures.tests # noqa: F401 105 | 106 | return fixtures.tests.test_suite() 107 | 108 | 109 | def load_tests(loader, standard_tests, pattern): 110 | standard_tests.addTests(loader.loadTestsFromNames(["fixtures.tests"])) 111 | return standard_tests 112 | -------------------------------------------------------------------------------- /fixtures/_fixtures/__init__.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, 2011, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | 17 | """Included fixtures.""" 18 | 19 | __all__ = [ 20 | "ByteStream", 21 | "DetailStream", 22 | "EnvironmentVariable", 23 | "EnvironmentVariableFixture", 24 | "FakeLogger", 25 | "FakePopen", 26 | "LoggerFixture", 27 | "LogHandler", 28 | "MockPatch", 29 | "MockPatchMultiple", 30 | "MockPatchObject", 31 | "MonkeyPatch", 32 | "NestedTempfile", 33 | "PackagePathEntry", 34 | "PopenFixture", 35 | "PythonPackage", 36 | "PythonPathEntry", 37 | "StringStream", 38 | "TempDir", 39 | "TempHomeDir", 40 | "Timeout", 41 | "TimeoutException", 42 | "WarningsCapture", 43 | "WarningsFilter", 44 | ] 45 | 46 | 47 | from fixtures._fixtures.environ import ( 48 | EnvironmentVariable, 49 | EnvironmentVariableFixture, 50 | ) 51 | from fixtures._fixtures.logger import ( 52 | FakeLogger, 53 | LoggerFixture, 54 | LogHandler, 55 | ) 56 | from fixtures._fixtures.mockpatch import ( 57 | MockPatch, 58 | MockPatchMultiple, 59 | MockPatchObject, 60 | ) 61 | from fixtures._fixtures.monkeypatch import MonkeyPatch 62 | from fixtures._fixtures.popen import ( 63 | FakePopen, 64 | PopenFixture, 65 | ) 66 | from fixtures._fixtures.packagepath import PackagePathEntry 67 | from fixtures._fixtures.pythonpackage import PythonPackage 68 | from fixtures._fixtures.pythonpath import PythonPathEntry 69 | from fixtures._fixtures.streams import ( 70 | ByteStream, 71 | DetailStream, 72 | StringStream, 73 | ) 74 | from fixtures._fixtures.tempdir import ( 75 | NestedTempfile, 76 | TempDir, 77 | ) 78 | from fixtures._fixtures.temphomedir import ( 79 | TempHomeDir, 80 | ) 81 | from fixtures._fixtures.timeout import ( 82 | Timeout, 83 | TimeoutException, 84 | ) 85 | from fixtures._fixtures.warnings import ( 86 | WarningsCapture, 87 | WarningsFilter, 88 | ) 89 | -------------------------------------------------------------------------------- /fixtures/_fixtures/environ.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, 2011, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | __all__ = [ 17 | "EnvironmentVariable", 18 | "EnvironmentVariableFixture", 19 | ] 20 | 21 | import os 22 | 23 | from fixtures import Fixture 24 | 25 | 26 | class EnvironmentVariable(Fixture): 27 | """Isolate a specific environment variable.""" 28 | 29 | def __init__(self, varname, newvalue=None): 30 | """Create an EnvironmentVariable fixture. 31 | 32 | :param varname: the name of the variable to isolate. 33 | :param newvalue: A value to set the variable to. If None, the variable 34 | will be deleted. 35 | 36 | During setup the variable will be deleted or assigned the requested 37 | value, and this will be restored in cleanUp. 38 | """ 39 | super(EnvironmentVariable, self).__init__() 40 | self.varname = varname 41 | self.newvalue = newvalue 42 | 43 | def _setUp(self): 44 | varname = self.varname 45 | orig_value = os.environ.get(varname) 46 | if orig_value is not None: 47 | self.addCleanup(os.environ.__setitem__, varname, orig_value) 48 | del os.environ[varname] 49 | else: 50 | self.addCleanup(os.environ.pop, varname, "") 51 | if self.newvalue is not None: 52 | os.environ[varname] = self.newvalue 53 | else: 54 | os.environ.pop(varname, "") 55 | 56 | 57 | EnvironmentVariableFixture = EnvironmentVariable 58 | -------------------------------------------------------------------------------- /fixtures/_fixtures/logger.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2011, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | from logging import StreamHandler, getLogger, INFO, Formatter 17 | import sys 18 | 19 | from fixtures import Fixture 20 | from fixtures._fixtures.streams import StringStream 21 | 22 | __all__ = [ 23 | "FakeLogger", 24 | "LoggerFixture", 25 | "LogHandler", 26 | ] 27 | 28 | 29 | class LogHandler(Fixture): 30 | """Replace a logger's handlers.""" 31 | 32 | def __init__(self, handler, name="", level=None, nuke_handlers=True): 33 | """Create a LogHandler fixture. 34 | 35 | :param handler: The handler to replace other handlers with. 36 | If nuke_handlers is False, then added as an extra handler. 37 | :param name: The name of the logger to replace. Defaults to "". 38 | :param level: The log level to set, defaults to not changing the level. 39 | :param nuke_handlers: If True remove all existing handles (prevents 40 | existing messages going to e.g. stdout). Defaults to True. 41 | """ 42 | super(LogHandler, self).__init__() 43 | self.handler = handler 44 | self._name = name 45 | self._level = level 46 | self._nuke_handlers = nuke_handlers 47 | 48 | def _setUp(self): 49 | logger = getLogger(self._name) 50 | if self._level: 51 | self.addCleanup(logger.setLevel, logger.level) 52 | logger.setLevel(self._level) 53 | if self._nuke_handlers: 54 | for handler in reversed(logger.handlers): 55 | self.addCleanup(logger.addHandler, handler) 56 | logger.removeHandler(handler) 57 | try: 58 | logger.addHandler(self.handler) 59 | finally: 60 | self.addCleanup(logger.removeHandler, self.handler) 61 | 62 | 63 | class StreamHandlerRaiseException(StreamHandler): 64 | """Handler class that will raise an exception on formatting errors.""" 65 | 66 | def handleError(self, record): 67 | _, value, tb = sys.exc_info() 68 | raise value.with_traceback(tb) 69 | 70 | 71 | class FakeLogger(Fixture): 72 | """Replace a logger and capture its output.""" 73 | 74 | def __init__( 75 | self, 76 | name="", 77 | level=INFO, 78 | format=None, 79 | datefmt=None, 80 | nuke_handlers=True, 81 | formatter=None, 82 | ): 83 | """Create a FakeLogger fixture. 84 | 85 | :param name: The name of the logger to replace. Defaults to "". 86 | :param level: The log level to set, defaults to INFO. 87 | :param format: Logging format to use. Defaults to capturing supplied 88 | messages verbatim. 89 | :param datefmt: Logging date format to use. 90 | Mirrors the datefmt used in python loggging. 91 | :param nuke_handlers: If True remove all existing handles (prevents 92 | existing messages going to e.g. stdout). Defaults to True. 93 | :param formatter: a custom log formatter class. Use this if you want 94 | to use a log Formatter other than the default one in python. 95 | 96 | Example: 97 | 98 | def test_log(self) 99 | fixture = self.useFixture(LoggerFixture()) 100 | logging.info('message') 101 | self.assertEqual('message', fixture.output) 102 | """ 103 | super(FakeLogger, self).__init__() 104 | self._name = name 105 | self._level = level 106 | self._format = format 107 | self._datefmt = datefmt 108 | self._nuke_handlers = nuke_handlers 109 | self._formatter = formatter 110 | 111 | def _setUp(self): 112 | name = "pythonlogging:'%s'" % self._name 113 | output = self.useFixture(StringStream(name)).stream 114 | self._output = output 115 | handler = StreamHandlerRaiseException(output) 116 | if self._format: 117 | formatter = self._formatter or Formatter 118 | handler.setFormatter(formatter(self._format, self._datefmt)) 119 | self.useFixture( 120 | LogHandler( 121 | handler, 122 | name=self._name, 123 | level=self._level, 124 | nuke_handlers=self._nuke_handlers, 125 | ) 126 | ) 127 | 128 | @property 129 | def output(self): 130 | self._output.seek(0) 131 | return self._output.read() 132 | 133 | def reset_output(self): 134 | self._output.truncate(0) 135 | 136 | 137 | LoggerFixture = FakeLogger 138 | -------------------------------------------------------------------------------- /fixtures/_fixtures/mockpatch.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010 United States Government as represented by the 2 | # Administrator of the National Aeronautics and Space Administration. 3 | # Copyright 2013 Hewlett-Packard Development Company, L.P. 4 | # All Rights Reserved. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 7 | # not use this file except in compliance with the License. You may obtain 8 | # a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15 | # License for the specific language governing permissions and limitations 16 | # under the License. 17 | 18 | import unittest.mock as mock 19 | 20 | import fixtures 21 | 22 | 23 | class _Base(fixtures.Fixture): 24 | def _setUp(self): 25 | _p = self._get_p() 26 | self.addCleanup(_p.stop) 27 | self.mock = _p.start() 28 | 29 | 30 | class MockPatchObject(_Base): 31 | """Deal with code around mock.""" 32 | 33 | def __init__(self, obj, attr, new=None, **kwargs): 34 | super(MockPatchObject, self).__init__() 35 | if new is None: 36 | new = mock.DEFAULT 37 | self._get_p = lambda: mock.patch.object(obj, attr, new, **kwargs) 38 | 39 | 40 | class MockPatch(_Base): 41 | """Deal with code around mock.patch.""" 42 | 43 | def __init__(self, obj, new=None, **kwargs): 44 | super(MockPatch, self).__init__() 45 | if new is None: 46 | new = mock.DEFAULT 47 | self._get_p = lambda: mock.patch(obj, new, **kwargs) 48 | 49 | 50 | class _MockPatchMultipleMeta(type): 51 | """Arrange for lazy loading of MockPatchMultiple.DEFAULT.""" 52 | 53 | # For strict backward compatibility, ensure that DEFAULT also works as 54 | # an instance property. 55 | def __new__(cls, name, bases, namespace, **kwargs): 56 | namespace["DEFAULT"] = cls.DEFAULT 57 | return super().__new__(cls, name, bases, namespace, **kwargs) 58 | 59 | # Default value to trigger a MagicMock to be created for a named 60 | # attribute. 61 | @property 62 | def DEFAULT(self): 63 | return mock.DEFAULT 64 | 65 | 66 | class MockPatchMultiple(_Base, metaclass=_MockPatchMultipleMeta): 67 | """Deal with code around mock.patch.multiple.""" 68 | 69 | def __init__(self, obj, **kwargs): 70 | """Initialize the mocks 71 | 72 | Pass name=value to replace obj.name with value. 73 | 74 | Pass name=Multiple.DEFAULT to replace obj.name with a 75 | MagicMock instance. 76 | 77 | :param obj: Object or name containing values being mocked. 78 | :type obj: str or object 79 | :param kwargs: names and values of attributes of obj to be mocked. 80 | 81 | """ 82 | super(MockPatchMultiple, self).__init__() 83 | self._get_p = lambda: mock.patch.multiple(obj, **kwargs) 84 | -------------------------------------------------------------------------------- /fixtures/_fixtures/monkeypatch.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | __all__ = [ 17 | "MonkeyPatch", 18 | ] 19 | 20 | import functools 21 | import types 22 | 23 | from fixtures import Fixture 24 | 25 | 26 | _class_types = (type,) 27 | 28 | 29 | def _coerce_values(obj, name, new_value, sentinel): 30 | """Return an adapted (new_value, old_value) for patching obj.name. 31 | 32 | setattr transforms a function into an instancemethod when set on a class. 33 | This checks if the attribute to be replaced is a callable descriptor - 34 | staticmethod, classmethod, or types.FunctionType - and wraps new_value if 35 | necessary. 36 | 37 | This also checks getattr(obj, name) and wraps it if necessary 38 | since the staticmethod wrapper isn't preserved. 39 | 40 | :param obj: The object with an attribute being patched. 41 | :param name: The name of the attribute being patched. 42 | :param new_value: The new value to be assigned. 43 | :param sentinel: If no old_value existed, the sentinel is returned to 44 | indicate that. 45 | """ 46 | old_value = getattr(obj, name, sentinel) 47 | if not isinstance(obj, _class_types): 48 | # We may be dealing with an instance of a class. In that case the 49 | # attribute may be the result of a descriptor lookup (or a __getattr__ 50 | # override etc). Its incomplete, but generally good enough to just 51 | # look and see if name is in the instance dict. 52 | try: 53 | obj.__dict__[name] 54 | except (AttributeError, KeyError): 55 | return (new_value, sentinel) 56 | else: 57 | return (new_value, old_value) 58 | 59 | # getattr() returns a function, this access pattern will return a 60 | # staticmethod/classmethod if the name method is defined that way 61 | old_attribute = obj.__dict__.get(name) 62 | if old_attribute is not None: 63 | old_value = old_attribute 64 | 65 | # If new_value is not callable, no special handling is needed. 66 | # (well, technically the same descriptor issue can happen with 67 | # user supplied descriptors, but that is arguably a feature - someone can 68 | # deliberately install a different descriptor. 69 | if not callable(new_value): 70 | return (new_value, old_value) 71 | 72 | if isinstance(old_value, staticmethod): 73 | new_value = staticmethod(new_value) 74 | elif isinstance(old_value, classmethod): 75 | new_value = classmethod(new_value) 76 | elif isinstance(old_value, types.FunctionType): 77 | if hasattr(new_value, "__get__"): 78 | # new_value is a descriptor, and that would result in it being 79 | # rebound if we assign it to a class - we want to preserve the 80 | # bound state rather than having it bound to the new object 81 | # it has been patched onto. 82 | captured_method = new_value 83 | 84 | @functools.wraps(old_value) 85 | def avoid_get(*args, **kwargs): 86 | return captured_method(*args, **kwargs) 87 | 88 | new_value = avoid_get 89 | 90 | return (new_value, old_value) 91 | 92 | 93 | class MonkeyPatch(Fixture): 94 | """Replace or delete an attribute.""" 95 | 96 | delete = object() 97 | 98 | def __init__(self, name, new_value=None): 99 | """Create a MonkeyPatch. 100 | 101 | :param name: The fully qualified object name to override. 102 | :param new_value: A value to set the name to. If set to 103 | MonkeyPatch.delete the attribute will be deleted. 104 | 105 | During setup the name will be deleted or assigned the requested value, 106 | and this will be restored in cleanUp. 107 | 108 | When patching methods, the call signature of name should be a subset 109 | of the parameters which can be used to call new_value. 110 | 111 | For instance. 112 | 113 | >>> class T: 114 | ... def method(self, arg1): 115 | ... pass 116 | >>> class N: 117 | ... @staticmethod 118 | ... def newmethod(arg1): 119 | ... pass 120 | 121 | Patching N.newmethod on top of T.method and then calling T().method(1) 122 | will not work because they do not have compatible call signatures - 123 | self will be passed to newmethod because the callable (N.newmethod) 124 | is placed onto T as a regular function. This allows capturing all the 125 | supplied parameters while still consulting local state in your 126 | new_value. 127 | """ 128 | Fixture.__init__(self) 129 | self.name = name 130 | self.new_value = new_value 131 | 132 | def _setUp(self): 133 | location, attribute = self.name.rsplit(".", 1) 134 | # Import, swallowing all errors as any element of location may be 135 | # a class or some such thing. 136 | try: 137 | __import__(location, {}, {}) 138 | except ImportError: 139 | pass 140 | 141 | components = location.split(".") 142 | current = __import__(components[0], {}, {}) 143 | for component in components[1:]: 144 | current = getattr(current, component) 145 | sentinel = object() 146 | new_value, old_value = _coerce_values( 147 | current, attribute, self.new_value, sentinel 148 | ) 149 | 150 | if self.new_value is self.delete: 151 | if old_value is not sentinel: 152 | delattr(current, attribute) 153 | else: 154 | setattr(current, attribute, new_value) 155 | 156 | if old_value is sentinel: 157 | self.addCleanup(self._safe_delete, current, attribute) 158 | else: 159 | self.addCleanup(setattr, current, attribute, old_value) 160 | 161 | def _safe_delete(self, obj, attribute): 162 | """Delete obj.attribute handling the case where its missing.""" 163 | sentinel = object() 164 | if getattr(obj, attribute, sentinel) is not sentinel: 165 | delattr(obj, attribute) 166 | -------------------------------------------------------------------------------- /fixtures/_fixtures/packagepath.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2011, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | __all__ = [ 17 | "PackagePathEntry", 18 | ] 19 | 20 | import sys 21 | 22 | from fixtures import Fixture 23 | 24 | 25 | class PackagePathEntry(Fixture): 26 | """Add a path to the path of a python package. 27 | 28 | The python package needs to be already imported. 29 | 30 | If this new path is already in the packages __path__ list then the __path__ 31 | list will not be altered. 32 | """ 33 | 34 | def __init__(self, packagename, directory): 35 | """Create a PackagePathEntry. 36 | 37 | :param directory: The directory to add to the package.__path__. 38 | """ 39 | self.packagename = packagename 40 | self.directory = directory 41 | 42 | def _setUp(self): 43 | path = sys.modules[self.packagename].__path__ 44 | if self.directory in path: 45 | return 46 | self.addCleanup(path.remove, self.directory) 47 | path.append(self.directory) 48 | -------------------------------------------------------------------------------- /fixtures/_fixtures/popen.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, 2011, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | __all__ = [ 17 | "FakePopen", 18 | "PopenFixture", 19 | ] 20 | 21 | import random 22 | import subprocess 23 | import sys 24 | 25 | from fixtures import Fixture 26 | 27 | 28 | class FakeProcess(object): 29 | """A test double process, roughly meeting subprocess.Popen's contract.""" 30 | 31 | def __init__(self, args, info): 32 | self._args = args 33 | self.stdin = info.get("stdin") 34 | self.stdout = info.get("stdout") 35 | self.stderr = info.get("stderr") 36 | self.pid = random.randint(0, 65536) 37 | self._returncode = info.get("returncode", 0) 38 | self.returncode = None 39 | 40 | @property 41 | def args(self): 42 | return self._args["args"] 43 | 44 | def poll(self): 45 | """Get the current value of FakeProcess.returncode. 46 | 47 | The returncode is None before communicate() and/or wait() are called, 48 | and it's set to the value provided by the 'info' dictionary otherwise 49 | (or 0 in case 'info' doesn't specify a value). 50 | """ 51 | return self.returncode 52 | 53 | def communicate(self, input=None, timeout=None): 54 | self.returncode = self._returncode 55 | if self.stdin and input: 56 | self.stdin.write(input) 57 | if self.stdout: 58 | out = self.stdout.getvalue() 59 | else: 60 | out = "" 61 | if self.stderr: 62 | err = self.stderr.getvalue() 63 | else: 64 | err = "" 65 | return out, err 66 | 67 | def __enter__(self): 68 | return self 69 | 70 | def __exit__(self, exc_type, exc_value, traceback): 71 | self.wait() 72 | 73 | def kill(self): 74 | pass 75 | 76 | def wait(self, timeout=None, endtime=None): 77 | if self.returncode is None: 78 | self.communicate() 79 | return self.returncode 80 | 81 | 82 | class FakePopen(Fixture): 83 | """Replace subprocess.Popen. 84 | 85 | Primarily useful for testing, this fixture replaces subprocess.Popen with a 86 | test double. 87 | 88 | :ivar procs: A list of the processes created by the fixture. 89 | """ 90 | 91 | _unpassed = object() 92 | 93 | def __init__(self, get_info=lambda _: {}): 94 | """Create a PopenFixture 95 | 96 | :param get_info: Optional callback to control the behaviour of the 97 | created process. This callback takes a kwargs dict for the Popen 98 | call, and should return a dict with any desired attributes. 99 | Only parameters that are supplied to the Popen call are in the 100 | dict, making it possible to detect the difference between 'passed 101 | with a default value' and 'not passed at all'. 102 | 103 | e.g. 104 | def get_info(proc_args): 105 | self.assertEqual(subprocess.PIPE, proc_args['stdin']) 106 | return {'stdin': StringIO('foobar')} 107 | 108 | The default behaviour if no get_info is supplied is for the return 109 | process to have returncode of None, empty streams and a random pid. 110 | 111 | After communicate() or wait() are called on the process object, 112 | the returncode is set to whatever get_info returns (or 0 if 113 | get_info is not supplied or doesn't return a dict with an explicit 114 | 'returncode' key). 115 | """ 116 | super(FakePopen, self).__init__() 117 | self.get_info = get_info 118 | 119 | def _setUp(self): 120 | self.addCleanup(setattr, subprocess, "Popen", subprocess.Popen) 121 | subprocess.Popen = self 122 | self.procs = [] 123 | 124 | # The method has the correct signature so we error appropriately if called 125 | # wrongly. 126 | def __call__( 127 | self, 128 | args, 129 | bufsize=_unpassed, 130 | executable=_unpassed, 131 | stdin=_unpassed, 132 | stdout=_unpassed, 133 | stderr=_unpassed, 134 | preexec_fn=_unpassed, 135 | close_fds=_unpassed, 136 | shell=_unpassed, 137 | cwd=_unpassed, 138 | env=_unpassed, 139 | universal_newlines=_unpassed, 140 | startupinfo=_unpassed, 141 | creationflags=_unpassed, 142 | restore_signals=_unpassed, 143 | start_new_session=_unpassed, 144 | pass_fds=_unpassed, 145 | *, 146 | group=_unpassed, 147 | extra_groups=_unpassed, 148 | user=_unpassed, 149 | umask=_unpassed, 150 | encoding=_unpassed, 151 | errors=_unpassed, 152 | text=_unpassed, 153 | pipesize=_unpassed, 154 | process_group=_unpassed, 155 | ): 156 | if sys.version_info < (3, 9): 157 | for arg_name in "group", "extra_groups", "user", "umask": 158 | if locals()[arg_name] is not FakePopen._unpassed: 159 | raise TypeError( 160 | "FakePopen.__call__() got an unexpected keyword " 161 | "argument '{}'".format(arg_name) 162 | ) 163 | if sys.version_info < (3, 10) and pipesize is not FakePopen._unpassed: 164 | raise TypeError( 165 | "FakePopen.__call__() got an unexpected keyword argument 'pipesize'" 166 | ) 167 | if sys.version_info < (3, 11) and process_group is not FakePopen._unpassed: 168 | raise TypeError( 169 | "FakePopen.__call__() got an unexpected keyword argument " 170 | "'process_group'" 171 | ) 172 | 173 | proc_args = dict(args=args) 174 | local = locals() 175 | for param in [ 176 | "bufsize", 177 | "executable", 178 | "stdin", 179 | "stdout", 180 | "stderr", 181 | "preexec_fn", 182 | "close_fds", 183 | "shell", 184 | "cwd", 185 | "env", 186 | "universal_newlines", 187 | "startupinfo", 188 | "creationflags", 189 | "restore_signals", 190 | "start_new_session", 191 | "pass_fds", 192 | "group", 193 | "extra_groups", 194 | "user", 195 | "umask", 196 | "encoding", 197 | "errors", 198 | "text", 199 | "pipesize", 200 | "process_group", 201 | ]: 202 | if local[param] is not FakePopen._unpassed: 203 | proc_args[param] = local[param] 204 | proc_info = self.get_info(proc_args) 205 | result = FakeProcess(proc_args, proc_info) 206 | self.procs.append(result) 207 | return result 208 | 209 | 210 | PopenFixture = FakePopen 211 | -------------------------------------------------------------------------------- /fixtures/_fixtures/pythonpackage.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | __all__ = [ 17 | "PythonPackage", 18 | ] 19 | 20 | import os.path 21 | 22 | from fixtures import Fixture 23 | from fixtures._fixtures.tempdir import TempDir 24 | 25 | 26 | class PythonPackage(Fixture): 27 | """Create a temporary Python package. 28 | 29 | :ivar base: The path of the directory containing the module. E.g. for a 30 | module 'foo', the path base + '/foo/__init__.py' would be the file path 31 | for the module. 32 | """ 33 | 34 | def __init__(self, packagename, modulelist, init=True): 35 | """Create a PythonPackage. 36 | 37 | :param packagename: The name of the package to create - e.g. 38 | 'toplevel.subpackage.' 39 | :param modulelist: List of modules to include in the package. 40 | Each module should be a tuple with the filename and content it 41 | should have. 42 | :param init: If false, do not create a missing __init__.py. When 43 | True, if modulelist does not include an __init__.py, an empty 44 | one is created. 45 | """ 46 | self.packagename = packagename 47 | self.modulelist = modulelist 48 | self.init = init 49 | 50 | def _setUp(self): 51 | self.base = self.useFixture(TempDir()).path 52 | base = self.base 53 | root = os.path.join(base, self.packagename) 54 | os.mkdir(root) 55 | init_seen = not self.init 56 | for modulename, contents in self.modulelist: 57 | stream = open(os.path.join(root, modulename), "wb") 58 | try: 59 | stream.write(contents) 60 | finally: 61 | stream.close() 62 | if modulename == "__init__.py": 63 | init_seen = True 64 | if not init_seen: 65 | open(os.path.join(root, "__init__.py"), "wb").close() 66 | -------------------------------------------------------------------------------- /fixtures/_fixtures/pythonpath.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2011, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | __all__ = [ 17 | "PythonPathEntry", 18 | ] 19 | 20 | import sys 21 | 22 | from fixtures import Fixture 23 | 24 | 25 | class PythonPathEntry(Fixture): 26 | """Add a path to sys.path. 27 | 28 | If the path is already in sys.path, sys.path will not be altered. 29 | """ 30 | 31 | def __init__(self, directory): 32 | """Create a PythonPathEntry. 33 | 34 | :param directory: The directory to add to sys.path. 35 | """ 36 | self.directory = directory 37 | 38 | def _setUp(self): 39 | if self.directory in sys.path: 40 | return 41 | self.addCleanup(sys.path.remove, self.directory) 42 | sys.path.append(self.directory) 43 | -------------------------------------------------------------------------------- /fixtures/_fixtures/streams.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2012, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | __all__ = [ 17 | "ByteStream", 18 | "DetailStream", 19 | "StringStream", 20 | ] 21 | 22 | import io 23 | 24 | from fixtures import Fixture 25 | 26 | 27 | class Stream(Fixture): 28 | """Expose a file-like object as a detail. 29 | 30 | :attr stream: The file-like object. 31 | """ 32 | 33 | def __init__(self, detail_name, stream_factory): 34 | """Create a ByteStream. 35 | 36 | :param detail_name: Use this as the name of the stream. 37 | :param stream_factory: Called to construct a pair of streams: 38 | (write_stream, content_stream). 39 | """ 40 | self._detail_name = detail_name 41 | self._stream_factory = stream_factory 42 | 43 | def _setUp(self): 44 | # Available with the fixtures[streams] extra. 45 | from testtools.content import content_from_stream 46 | 47 | write_stream, read_stream = self._stream_factory() 48 | self.stream = write_stream 49 | self.addDetail( 50 | self._detail_name, content_from_stream(read_stream, seek_offset=0) 51 | ) 52 | 53 | 54 | def _byte_stream_factory(): 55 | result = io.BytesIO() 56 | return (result, result) 57 | 58 | 59 | def ByteStream(detail_name): 60 | """Provide a file-like object that accepts bytes and expose as a detail. 61 | 62 | :param detail_name: The name of the detail. 63 | :return: A fixture which has an attribute `stream` containing the file-like 64 | object. 65 | """ 66 | return Stream(detail_name, _byte_stream_factory) 67 | 68 | 69 | def _string_stream_factory(): 70 | lower = io.BytesIO() 71 | upper = io.TextIOWrapper(lower, encoding="utf8") 72 | # See http://bugs.python.org/issue7955 73 | upper._CHUNK_SIZE = 1 74 | return upper, lower 75 | 76 | 77 | def StringStream(detail_name): 78 | """Provide a file-like object that accepts strings and expose as a detail. 79 | 80 | :param detail_name: The name of the detail. 81 | :return: A fixture which has an attribute `stream` containing the file-like 82 | object. 83 | """ 84 | return Stream(detail_name, _string_stream_factory) 85 | 86 | 87 | def DetailStream(detail_name): 88 | """Deprecated alias for ByteStream.""" 89 | return ByteStream(detail_name) 90 | -------------------------------------------------------------------------------- /fixtures/_fixtures/tempdir.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | __all__ = [ 17 | "NestedTempfile", 18 | "TempDir", 19 | ] 20 | 21 | import os 22 | import shutil 23 | import tempfile 24 | 25 | import fixtures 26 | 27 | 28 | class TempDir(fixtures.Fixture): 29 | """Create a temporary directory. 30 | 31 | :ivar path: The path of the temporary directory. 32 | """ 33 | 34 | def __init__(self, rootdir=None): 35 | """Create a TempDir. 36 | 37 | :param rootdir: If supplied force the temporary directory to be a 38 | child of rootdir. 39 | """ 40 | self.rootdir = rootdir 41 | 42 | def _setUp(self): 43 | self.path = tempfile.mkdtemp(dir=self.rootdir) 44 | self.addCleanup(shutil.rmtree, self.path, ignore_errors=True) 45 | 46 | def join(self, *children): 47 | """Return an absolute path, given one relative to this ``TempDir``. 48 | 49 | WARNING: This does not do any checking of ``children`` to make sure 50 | they aren't walking up the tree using path segments like '..' or 51 | '/usr'. Use at your own risk. 52 | """ 53 | return os.path.abspath(os.path.join(self.path, *children)) 54 | 55 | 56 | class NestedTempfile(fixtures.Fixture): 57 | """Nest all temporary files and directories inside another directory. 58 | 59 | This temporarily monkey-patches the default location that the `tempfile` 60 | package creates temporary files and directories in to be a new temporary 61 | directory. This new temporary directory is removed when the fixture is torn 62 | down. 63 | """ 64 | 65 | def _setUp(self): 66 | tempdir = self.useFixture(TempDir()).path 67 | patch = fixtures.MonkeyPatch("tempfile.tempdir", tempdir) 68 | self.useFixture(patch) 69 | -------------------------------------------------------------------------------- /fixtures/_fixtures/temphomedir.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, Canonical Ltd. 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | __all__ = [ 17 | "TempHomeDir", 18 | ] 19 | 20 | import fixtures 21 | from fixtures._fixtures.tempdir import TempDir 22 | 23 | 24 | class TempHomeDir(TempDir): 25 | """Create a temporary directory and set it as $HOME 26 | 27 | :ivar path: the path of the temporary directory. 28 | """ 29 | 30 | def _setUp(self): 31 | super(TempHomeDir, self)._setUp() 32 | self.useFixture(fixtures.EnvironmentVariable("HOME", self.path)) 33 | -------------------------------------------------------------------------------- /fixtures/_fixtures/timeout.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (C) 2011, Martin Pool 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | 17 | """Timeout fixture.""" 18 | 19 | import signal 20 | 21 | import fixtures 22 | 23 | __all__ = [ 24 | "Timeout", 25 | "TimeoutException", 26 | ] 27 | 28 | 29 | class TimeoutException(Exception): 30 | """Timeout expired""" 31 | 32 | 33 | class Timeout(fixtures.Fixture): 34 | """Fixture that aborts the contained code after a number of seconds. 35 | 36 | The interrupt can be either gentle, in which case TimeoutException is 37 | raised, or not gentle, in which case the process will typically be aborted 38 | by SIGALRM. 39 | 40 | Cautions: 41 | * This has no effect on Windows. 42 | * Only one Timeout can be used at any time per process. 43 | """ 44 | 45 | def __init__(self, timeout_secs, gentle): 46 | self.timeout_secs = timeout_secs 47 | self.alarm_fn = getattr(signal, "alarm", None) 48 | self.gentle = gentle 49 | 50 | def signal_handler(self, signum, frame): 51 | raise TimeoutException() 52 | 53 | def _setUp(self): 54 | if self.alarm_fn is None: 55 | return # Can't run on Windows 56 | if self.gentle: 57 | # Install a handler for SIGARLM so we can raise an exception rather 58 | # than the default handler executing, which kills the process. 59 | old_handler = signal.signal(signal.SIGALRM, self.signal_handler) 60 | # We add the slarm cleanup before the cleanup for the signal handler, 61 | # otherwise there is a race condition where the signal handler is 62 | # cleaned up but the alarm still fires. 63 | self.addCleanup(lambda: self.alarm_fn(0)) 64 | self.alarm_fn(self.timeout_secs) 65 | if self.gentle: 66 | self.addCleanup(lambda: signal.signal(signal.SIGALRM, old_handler)) 67 | -------------------------------------------------------------------------------- /fixtures/_fixtures/warnings.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 4 | # license at the users choice. A copy of both licenses are available in the 5 | # project source as Apache-2.0 and BSD. You may not use this file except in 6 | # compliance with one of these two licences. 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 10 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 11 | # license you chose for the specific language governing permissions and 12 | # limitations under that license. 13 | 14 | __all__ = [ 15 | "WarningsCapture", 16 | "WarningsFilter", 17 | ] 18 | 19 | import warnings 20 | 21 | import fixtures 22 | 23 | 24 | class WarningsCapture(fixtures.Fixture): 25 | """Capture warnings. 26 | 27 | While ``WarningsCapture`` is active, warnings will be captured by 28 | the fixture (so that they can be later analyzed). 29 | 30 | :attribute captures: A list of warning capture ``WarningMessage`` objects. 31 | """ 32 | 33 | def _showwarning(self, *args, **kwargs): 34 | self.captures.append(warnings.WarningMessage(*args, **kwargs)) 35 | 36 | def _setUp(self): 37 | patch = fixtures.MonkeyPatch("warnings.showwarning", self._showwarning) 38 | self.useFixture(patch) 39 | self.captures = [] 40 | 41 | 42 | class WarningsFilter(fixtures.Fixture): 43 | """Configure warnings filters. 44 | 45 | While ``WarningsFilter`` is active, warnings will be filtered per 46 | configuration. 47 | """ 48 | 49 | def __init__(self, filters=None): 50 | """Create a WarningsFilter fixture. 51 | 52 | :param filters: An optional list of dictionaries with arguments 53 | corresponding to the arguments to 54 | :py:func:`warnings.filterwarnings`. For example:: 55 | 56 | [ 57 | { 58 | 'action': 'ignore', 59 | 'message': 'foo', 60 | 'category': DeprecationWarning, 61 | }, 62 | ] 63 | 64 | Order is important: entries closer to the front of the list 65 | override entries later in the list, if both match a particular 66 | warning. 67 | 68 | Alternatively, you can configure warnings within the context of the 69 | fixture. 70 | 71 | See `the Python documentation`__ for more information. 72 | 73 | __: https://docs.python.org/3/library/warnings.html#the-warnings-filter 74 | """ 75 | super().__init__() 76 | self.filters = filters or [] 77 | 78 | def _setUp(self): 79 | self._original_warning_filters = warnings.filters[:] 80 | 81 | for filt in self.filters: 82 | warnings.filterwarnings(**filt) 83 | 84 | self.addCleanup(self._reset_warning_filters) 85 | 86 | def _reset_warning_filters(self): 87 | warnings.filters[:] = self._original_warning_filters 88 | -------------------------------------------------------------------------------- /fixtures/callmany.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | __all__ = [ 17 | "CallMany", 18 | ] 19 | 20 | import sys 21 | 22 | 23 | try: 24 | from testtools import MultipleExceptions 25 | except ImportError: 26 | 27 | class MultipleExceptions(Exception): 28 | """Report multiple exc_info tuples in self.args.""" 29 | 30 | 31 | class CallMany(object): 32 | """A stack of functions which will all be called on __call__. 33 | 34 | CallMany also acts as a context manager for convenience. 35 | 36 | Functions are called in last pushed first executed order. 37 | 38 | This is used by Fixture to manage its addCleanup feature. 39 | """ 40 | 41 | def __init__(self): 42 | self._cleanups = [] 43 | 44 | def push(self, cleanup, *args, **kwargs): 45 | """Add a function to be called from __call__. 46 | 47 | On __call__ all functions are called - see __call__ for details on how 48 | multiple exceptions are handled. 49 | 50 | :param cleanup: A callable to call during cleanUp. 51 | :param *args: Positional args for cleanup. 52 | :param kwargs: Keyword args for cleanup. 53 | :return: None 54 | """ 55 | self._cleanups.append((cleanup, args, kwargs)) 56 | 57 | def __call__(self, raise_errors=True): 58 | """Run all the registered functions. 59 | 60 | :param raise_errors: Deprecated parameter from before testtools gained 61 | MultipleExceptions. raise_errors defaults to True. When True 62 | if exception(s) are raised while running functions, they are 63 | re-raised after all the functions have run. If multiple exceptions 64 | are raised, they are all wrapped into a MultipleExceptions object, 65 | and that is raised. 66 | 67 | Thus, to catch a specific exception from a function run by __call__, 68 | you need to catch both the exception and MultipleExceptions, and 69 | then check within a MultipleExceptions instance for an occurrence of 70 | the type you wish to catch. 71 | :return: Either None or a list of the exc_info() for each exception 72 | that occurred if raise_errors was False. 73 | """ 74 | cleanups = reversed(self._cleanups) 75 | self._cleanups = [] 76 | result = [] 77 | for cleanup, args, kwargs in cleanups: 78 | try: 79 | cleanup(*args, **kwargs) 80 | except Exception: 81 | result.append(sys.exc_info()) 82 | if result and raise_errors: 83 | if 1 == len(result): 84 | error = result[0] 85 | raise error[1].with_traceback(error[2]) 86 | else: 87 | raise MultipleExceptions(*result) 88 | if not raise_errors: 89 | return result 90 | 91 | def __enter__(self): 92 | return self 93 | 94 | def __exit__(self, exc_type, exc_val, exc_tb): 95 | self() 96 | return False # Propagate exceptions from the with body. 97 | -------------------------------------------------------------------------------- /fixtures/fixture.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | __all__ = [ 17 | "CompoundFixture", 18 | "Fixture", 19 | "FunctionFixture", 20 | "MethodFixture", 21 | "MultipleExceptions", 22 | "SetupError", 23 | ] 24 | 25 | import itertools 26 | import sys 27 | 28 | from fixtures.callmany import ( 29 | CallMany, 30 | # Deprecated, imported for compatibility. 31 | MultipleExceptions, 32 | ) 33 | 34 | 35 | try: 36 | from testtools.testcase import gather_details 37 | except ImportError: 38 | gather_details = None 39 | 40 | 41 | # This would be better in testtools (or a common library) 42 | def combine_details(source_details, target_details): 43 | """Add every value from source to target deduping common keys.""" 44 | for name, content_object in source_details.items(): 45 | new_name = name 46 | disambiguator = itertools.count(1) 47 | while new_name in target_details: 48 | new_name = "%s-%d" % (name, next(disambiguator)) 49 | name = new_name 50 | target_details[name] = content_object 51 | 52 | 53 | class SetupError(Exception): 54 | """Setup failed. 55 | 56 | args[0] will be a details dict. 57 | """ 58 | 59 | 60 | class Fixture(object): 61 | """A Fixture representing some state or resource. 62 | 63 | Often used in tests, a Fixture must be setUp before using it, and cleanUp 64 | called after it is finished with (because many Fixture classes have 65 | external resources such as temporary directories). 66 | 67 | The reset() method can be called to perform cleanUp and setUp automatically 68 | and potentially faster. 69 | """ 70 | 71 | def addCleanup(self, cleanup, *args, **kwargs): 72 | """Add a clean function to be called from cleanUp. 73 | 74 | All cleanup functions are called - see cleanUp for details on how 75 | multiple exceptions are handled. 76 | 77 | If for some reason you need to cancel cleanups, call 78 | self._clear_cleanups. 79 | 80 | :param cleanup: A callable to call during cleanUp. 81 | :param args: Positional args for cleanup. 82 | :param kwargs: Keyword args for cleanup. 83 | :return: None 84 | """ 85 | self._cleanups.push(cleanup, *args, **kwargs) 86 | 87 | def addDetail(self, name, content_object): 88 | """Add a detail to the Fixture. 89 | 90 | This may only be called after setUp has been called. 91 | 92 | :param name: The name for the detail being added. Overrides existing 93 | identically named details. 94 | :param content_object: The content object (meeting the 95 | testtools.content.Content protocol) being added. 96 | """ 97 | self._details[name] = content_object 98 | 99 | def cleanUp(self, raise_first=True): 100 | """Cleanup the fixture. 101 | 102 | This function will free all resources managed by the Fixture, restoring 103 | it (and any external facilities such as databases, temporary 104 | directories and so forth_ to their original state. 105 | 106 | This should not typically be overridden, see addCleanup instead. 107 | 108 | cleanUp may be called once and only once after setUp() has been called. 109 | The base implementation of setUp will automatically call cleanUp if 110 | an exception occurs within setUp itself. 111 | 112 | :param raise_first: Deprecated parameter from before testtools gained 113 | MultipleExceptions. raise_first defaults to True. When True 114 | if a single exception is raised, it is reraised after all the 115 | cleanUps have run. If multiple exceptions are raised, they are 116 | all wrapped into a MultipleExceptions object, and that is reraised. 117 | Thus, to catch a specific exception from cleanUp, you need to catch 118 | both the exception and MultipleExceptions, and then check within 119 | a MultipleExceptions instance for the type you're catching. 120 | :return: A list of the exc_info() for each exception that occurred if 121 | raise_first was False 122 | """ 123 | try: 124 | return self._cleanups(raise_errors=raise_first) 125 | finally: 126 | self._remove_state() 127 | 128 | def _clear_cleanups(self): 129 | """Clean the cleanup queue without running them. 130 | 131 | This is a helper that can be useful for subclasses which define 132 | reset(): they may perform something equivalent to a typical cleanUp 133 | without actually calling the cleanups. 134 | 135 | This also clears the details dict. 136 | """ 137 | self._cleanups = CallMany() 138 | self._details = {} 139 | self._detail_sources = [] 140 | 141 | def _remove_state(self): 142 | """Remove the internal state. 143 | 144 | Called from cleanUp to put the fixture back into a not-ready state. 145 | """ 146 | self._cleanups = None 147 | self._details = None 148 | self._detail_sources = None 149 | 150 | def __enter__(self): 151 | self.setUp() 152 | return self 153 | 154 | def __exit__(self, exc_type, exc_val, exc_tb): 155 | try: 156 | self._cleanups() 157 | finally: 158 | self._remove_state() 159 | return False # propagate exceptions from the with body. 160 | 161 | def getDetails(self): 162 | """Get the current details registered with the fixture. 163 | 164 | This does not return the internal dictionary: mutating it will have no 165 | effect. If you need to mutate it, just do so directly. 166 | 167 | :return: Dict from name -> content_object. 168 | """ 169 | result = dict(self._details) 170 | for source in self._detail_sources: 171 | combine_details(source.getDetails(), result) 172 | return result 173 | 174 | def setUp(self): 175 | """Prepare the Fixture for use. 176 | 177 | This should not be overridden. Concrete fixtures should implement 178 | _setUp. Overriding of setUp is still supported, just not recommended. 179 | 180 | After setUp has completed, the fixture will have one or more attributes 181 | which can be used (these depend totally on the concrete subclass). 182 | 183 | :raises: MultipleExceptions if _setUp fails. The last exception 184 | captured within the MultipleExceptions will be a SetupError 185 | exception. 186 | :return: None. 187 | 188 | :changed in 1.3: The recommendation to override setUp has been 189 | reversed - before 1.3, setUp() should be overridden, now it should 190 | not be. 191 | :changed in 1.3.1: BaseException is now caught, and only subclasses of 192 | Exception are wrapped in MultipleExceptions. 193 | """ 194 | self._clear_cleanups() 195 | try: 196 | self._setUp() 197 | except BaseException: 198 | err = sys.exc_info() 199 | details = {} 200 | if gather_details is not None: 201 | # Materialise all details since we're about to cleanup. 202 | gather_details(self.getDetails(), details) 203 | else: 204 | details = self.getDetails() 205 | errors = [err] + self.cleanUp(raise_first=False) 206 | try: 207 | raise SetupError(details) 208 | except SetupError: 209 | errors.append(sys.exc_info()) 210 | if issubclass(err[0], Exception): 211 | raise MultipleExceptions(*errors) 212 | else: 213 | raise err[1].with_traceback(err[2]) 214 | 215 | def _setUp(self): 216 | """Template method for subclasses to override. 217 | 218 | Override this to customise the fixture. When overriding 219 | be sure to include self.addCleanup calls to restore the fixture to 220 | an un-setUp state, so that a single Fixture instance can be reused. 221 | 222 | Fixtures will never have a body in _setUp - calling super() is 223 | entirely at the discretion of subclasses. 224 | 225 | :return: None. 226 | """ 227 | 228 | def reset(self): 229 | """Reset a setUp Fixture to the 'just setUp' state again. 230 | 231 | The default implementation calls 232 | self.cleanUp() 233 | self.setUp() 234 | 235 | but this function may be overridden to provide an optimised routine to 236 | achieve the same result. 237 | 238 | :return: None. 239 | """ 240 | self.cleanUp() 241 | self.setUp() 242 | 243 | def useFixture(self, fixture): 244 | """Use another fixture. 245 | 246 | The fixture will be setUp, and self.addCleanup(fixture.cleanUp) called. 247 | If the fixture fails to set up, useFixture will attempt to gather its 248 | details into this fixture's details to aid in debugging. 249 | 250 | :param fixture: The fixture to use. 251 | :return: The fixture, after setting it up and scheduling a cleanup for 252 | it. 253 | :raises: Any errors raised by the fixture's setUp method. 254 | """ 255 | try: 256 | fixture.setUp() 257 | except MultipleExceptions as e: 258 | if e.args[-1][0] is SetupError: 259 | combine_details(e.args[-1][1].args[0], self._details) 260 | raise 261 | except: 262 | # The child failed to come up and didn't raise MultipleExceptions 263 | # which we can understand... capture any details it has (copying 264 | # the content, it may go away anytime). 265 | if gather_details is not None: 266 | gather_details(fixture.getDetails(), self._details) 267 | raise 268 | else: 269 | self.addCleanup(fixture.cleanUp) 270 | # Calls to getDetails while this fixture is setup will return 271 | # details from the child fixture. 272 | self._detail_sources.append(fixture) 273 | return fixture 274 | 275 | 276 | class FunctionFixture(Fixture): 277 | """An adapter to use function(s) as a Fixture. 278 | 279 | Typically used when an existing object or function interface exists but you 280 | wish to use it as a Fixture (e.g. because fixtures are in use in your test 281 | suite and this will fit in better). 282 | 283 | To adapt an object with differently named setUp and cleanUp methods: 284 | fixture = FunctionFixture(object.install, object.__class__.remove) 285 | Note that the indirection via __class__ is to get an unbound method 286 | which can accept the result from install. See also MethodFixture which 287 | is specialised for objects. 288 | 289 | To adapt functions: 290 | fixture = FunctionFixture(tempfile.mkdtemp, shutil.rmtree) 291 | 292 | With a reset function: 293 | fixture = FunctionFixture(setup, cleanup, reset) 294 | 295 | :ivar fn_result: The result of the setup_fn. Undefined outside of the 296 | setUp, cleanUp context. 297 | """ 298 | 299 | def __init__(self, setup_fn, cleanup_fn=None, reset_fn=None): 300 | """Create a FunctionFixture. 301 | 302 | :param setup_fn: A callable which takes no parameters and returns the 303 | thing you want to use. e.g. 304 | def setup_fn(): 305 | return 42 306 | The result of setup_fn is assigned to the fn_result attribute bu 307 | FunctionFixture.setUp. 308 | :param cleanup_fn: Optional callable which takes a single parameter, 309 | which must be that which is returned from the setup_fn. This is 310 | called from cleanUp. 311 | :param reset_fn: Optional callable which takes a single parameter like 312 | cleanup_fn, but also returns a new object for use as the fn_result: 313 | if defined this replaces the use of cleanup_fn and setup_fn when 314 | reset() is called. 315 | """ 316 | super(FunctionFixture, self).__init__() 317 | self.setup_fn = setup_fn 318 | self.cleanup_fn = cleanup_fn 319 | self.reset_fn = reset_fn 320 | 321 | def _setUp(self): 322 | fn_result = self.setup_fn() 323 | self._maybe_cleanup(fn_result) 324 | 325 | def reset(self): 326 | if self.reset_fn is None: 327 | super(FunctionFixture, self).reset() 328 | else: 329 | self._clear_cleanups() 330 | fn_result = self.reset_fn(self.fn_result) 331 | self._maybe_cleanup(fn_result) 332 | 333 | def _maybe_cleanup(self, fn_result): 334 | self.addCleanup(delattr, self, "fn_result") 335 | if self.cleanup_fn is not None: 336 | self.addCleanup(self.cleanup_fn, fn_result) 337 | self.fn_result = fn_result 338 | 339 | 340 | class MethodFixture(Fixture): 341 | """An adapter to use a function as a Fixture. 342 | 343 | Typically used when an existing object exists but you wish to use it as a 344 | Fixture (e.g. because fixtures are in use in your test suite and this will 345 | fit in better). 346 | 347 | To adapt an object with setUp / tearDown methods: 348 | fixture = MethodFixture(object) 349 | If setUp / tearDown / reset are missing, they simply won't be called. 350 | 351 | The object is exposed on fixture.obj. 352 | 353 | To adapt an object with differently named setUp and cleanUp methods: 354 | fixture = MethodFixture(object, setup=object.mySetUp, 355 | teardown=object.myTearDown) 356 | 357 | With a differently named reset function: 358 | fixture = MethodFixture(object, reset=object.myReset) 359 | 360 | :ivar obj: The object which is being wrapped. 361 | """ 362 | 363 | def __init__(self, obj, setup=None, cleanup=None, reset=None): 364 | """Create a MethodFixture. 365 | 366 | :param obj: The object to wrap. Exposed as fixture.obj 367 | :param setup: A method which takes no parameters. e.g. 368 | def setUp(self): 369 | self.value = 42 370 | If setup is not supplied, and the object has a setUp method, that 371 | method is used, otherwise nothing will happen during fixture.setUp. 372 | :param cleanup: Optional method to cleanup the object's state. If 373 | not supplied the method 'tearDown' is used if it exists. 374 | :param reset: Optional method to reset the wrapped object for use. 375 | If not supplied, then the method 'reset' is used if it exists, 376 | otherwise cleanUp and setUp are called as per Fixture.reset(). 377 | """ 378 | super(MethodFixture, self).__init__() 379 | self.obj = obj 380 | if setup is None: 381 | setup = getattr(obj, "setUp", None) 382 | if setup is None: 383 | 384 | def setup(): 385 | return None 386 | 387 | self._setup = setup 388 | if cleanup is None: 389 | cleanup = getattr(obj, "tearDown", None) 390 | if cleanup is None: 391 | 392 | def cleanup(): 393 | return None 394 | 395 | self._cleanup = cleanup 396 | if reset is None: 397 | reset = getattr(obj, "reset", None) 398 | self._reset = reset 399 | 400 | def _setUp(self): 401 | self._setup() 402 | 403 | def cleanUp(self): 404 | super(MethodFixture, self).cleanUp() 405 | self._cleanup() 406 | 407 | def reset(self): 408 | if self._reset is None: 409 | super(MethodFixture, self).reset() 410 | else: 411 | self._reset() 412 | 413 | 414 | class CompoundFixture(Fixture): 415 | """A fixture that combines many fixtures. 416 | 417 | :ivar fixtures: The list of fixtures that make up this one. (read only). 418 | """ 419 | 420 | def __init__(self, fixtures): 421 | """Construct a fixture made of many fixtures. 422 | 423 | :param fixtures: An iterable of fixtures. 424 | """ 425 | super(CompoundFixture, self).__init__() 426 | self.fixtures = list(fixtures) 427 | 428 | def _setUp(self): 429 | for fixture in self.fixtures: 430 | self.useFixture(fixture) 431 | -------------------------------------------------------------------------------- /fixtures/testcase.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | __all__ = [ 17 | "TestWithFixtures", 18 | ] 19 | 20 | import unittest 21 | 22 | from fixtures.fixture import gather_details 23 | 24 | 25 | class TestWithFixtures(unittest.TestCase): 26 | """A TestCase with a helper function to use fixtures. 27 | 28 | Normally used as a mix-in class to add useFixture. 29 | 30 | Note that test classes such as testtools.TestCase which already have a 31 | ``useFixture`` method do not need this mixed in. 32 | """ 33 | 34 | def useFixture(self, fixture): 35 | """Use fixture in a test case. 36 | 37 | The fixture will be setUp, and self.addCleanup(fixture.cleanUp) called. 38 | 39 | :param fixture: The fixture to use. 40 | :return: The fixture, after setting it up and scheduling a cleanup for 41 | it. 42 | """ 43 | use_details = ( 44 | gather_details is not None and getattr(self, "addDetail", None) is not None 45 | ) 46 | try: 47 | fixture.setUp() 48 | except: 49 | if use_details: 50 | # Capture the details now, in case the fixture goes away. 51 | gather_details(fixture.getDetails(), self.getDetails()) 52 | raise 53 | else: 54 | self.addCleanup(fixture.cleanUp) 55 | if use_details: 56 | # Capture the details from the fixture during test teardown; 57 | # this will evaluate the details before tearing down the 58 | # fixture. 59 | self.addCleanup(gather_details, fixture, self) 60 | return fixture 61 | -------------------------------------------------------------------------------- /fixtures/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | import doctest 17 | import unittest 18 | 19 | 20 | def test_suite(): 21 | standard_tests = unittest.TestSuite() 22 | loader = unittest.TestLoader() 23 | return load_tests(loader, standard_tests, None) 24 | 25 | 26 | def load_tests(loader, standard_tests, pattern): 27 | test_modules = [ 28 | "callmany", 29 | "fixture", 30 | "testcase", 31 | ] 32 | prefix = "fixtures.tests.test_" 33 | test_mod_names = [prefix + test_module for test_module in test_modules] 34 | standard_tests.addTests(loader.loadTestsFromNames(test_mod_names)) 35 | standard_tests.addTests(loader.loadTestsFromName("fixtures.tests._fixtures")) 36 | doctest.set_unittest_reportflags(doctest.REPORT_ONLY_FIRST_FAILURE) 37 | standard_tests.addTest(doctest.DocFileSuite("../../README.rst")) 38 | return standard_tests 39 | -------------------------------------------------------------------------------- /fixtures/tests/_fixtures/__init__.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | import os 17 | 18 | 19 | def load_tests(loader, standard_tests, pattern): 20 | test_modules = [ 21 | os.path.splitext(path)[0] 22 | for path in os.listdir(os.path.dirname(__file__)) 23 | if path.startswith("test_") 24 | ] 25 | prefix = "fixtures.tests._fixtures." 26 | test_mod_names = [prefix + test_module for test_module in test_modules] 27 | standard_tests.addTests(loader.loadTestsFromNames(test_mod_names)) 28 | return standard_tests 29 | -------------------------------------------------------------------------------- /fixtures/tests/_fixtures/test_environ.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, 2011, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | import os 17 | 18 | import testtools 19 | 20 | from fixtures import EnvironmentVariable, TestWithFixtures 21 | 22 | 23 | class TestEnvironmentVariable(testtools.TestCase, TestWithFixtures): 24 | def test_setup_ignores_missing(self): 25 | fixture = EnvironmentVariable("FIXTURES_TEST_VAR") 26 | os.environ.pop("FIXTURES_TEST_VAR", "") 27 | self.useFixture(fixture) 28 | self.assertEqual(None, os.environ.get("FIXTURES_TEST_VAR")) 29 | 30 | def test_setup_sets_when_missing(self): 31 | fixture = EnvironmentVariable("FIXTURES_TEST_VAR", "bar") 32 | os.environ.pop("FIXTURES_TEST_VAR", "") 33 | self.useFixture(fixture) 34 | self.assertEqual("bar", os.environ.get("FIXTURES_TEST_VAR")) 35 | 36 | def test_setup_deletes(self): 37 | fixture = EnvironmentVariable("FIXTURES_TEST_VAR") 38 | os.environ["FIXTURES_TEST_VAR"] = "foo" 39 | self.useFixture(fixture) 40 | self.assertEqual(None, os.environ.get("FIXTURES_TEST_VAR")) 41 | 42 | def test_setup_overrides(self): 43 | fixture = EnvironmentVariable("FIXTURES_TEST_VAR", "bar") 44 | os.environ["FIXTURES_TEST_VAR"] = "foo" 45 | self.useFixture(fixture) 46 | self.assertEqual("bar", os.environ.get("FIXTURES_TEST_VAR")) 47 | 48 | def test_cleanup_deletes_when_missing(self): 49 | fixture = EnvironmentVariable("FIXTURES_TEST_VAR") 50 | os.environ.pop("FIXTURES_TEST_VAR", "") 51 | with fixture: 52 | os.environ["FIXTURES_TEST_VAR"] = "foo" 53 | self.assertEqual(None, os.environ.get("FIXTURES_TEST_VAR")) 54 | 55 | def test_cleanup_deletes_when_set(self): 56 | fixture = EnvironmentVariable("FIXTURES_TEST_VAR", "bar") 57 | os.environ.pop("FIXTURES_TEST_VAR", "") 58 | with fixture: 59 | os.environ["FIXTURES_TEST_VAR"] = "foo" 60 | self.assertEqual(None, os.environ.get("FIXTURES_TEST_VAR")) 61 | 62 | def test_cleanup_restores_when_missing(self): 63 | fixture = EnvironmentVariable("FIXTURES_TEST_VAR") 64 | os.environ["FIXTURES_TEST_VAR"] = "bar" 65 | with fixture: 66 | os.environ.pop("FIXTURES_TEST_VAR", "") 67 | self.assertEqual("bar", os.environ.get("FIXTURES_TEST_VAR")) 68 | 69 | def test_cleanup_restores_when_set(self): 70 | fixture = EnvironmentVariable("FIXTURES_TEST_VAR") 71 | os.environ["FIXTURES_TEST_VAR"] = "bar" 72 | with fixture: 73 | os.environ["FIXTURES_TEST_VAR"] = "quux" 74 | self.assertEqual("bar", os.environ.get("FIXTURES_TEST_VAR")) 75 | -------------------------------------------------------------------------------- /fixtures/tests/_fixtures/test_logger.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2011, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | import io 17 | import logging 18 | import time 19 | 20 | import testtools 21 | from testtools import TestCase 22 | 23 | from fixtures import ( 24 | FakeLogger, 25 | LogHandler, 26 | TestWithFixtures, 27 | ) 28 | 29 | 30 | # A simple custom formatter that prepends Foo to all log messages, for 31 | # testing formatter overrides. 32 | class FooFormatter(logging.Formatter): 33 | def format(self, record): 34 | self._style = logging.PercentStyle("Foo " + self._style._fmt) 35 | self._fmt = self._style._fmt 36 | return logging.Formatter.format(self, record) 37 | 38 | 39 | class FakeLoggerTest(TestCase, TestWithFixtures): 40 | def setUp(self): 41 | super(FakeLoggerTest, self).setUp() 42 | self.logger = logging.getLogger() 43 | self.addCleanup(self.removeHandlers, self.logger) 44 | 45 | def removeHandlers(self, logger): 46 | for handler in logger.handlers: 47 | logger.removeHandler(handler) 48 | 49 | def test_output_property_has_output(self): 50 | fixture = self.useFixture(FakeLogger()) 51 | logging.info("some message") 52 | self.assertEqual("some message\n", fixture.output) 53 | 54 | def test_replace_and_restore_handlers(self): 55 | stream = io.StringIO() 56 | logger = logging.getLogger() 57 | logger.addHandler(logging.StreamHandler(stream)) 58 | logger.setLevel(logging.INFO) 59 | logging.info("one") 60 | fixture = FakeLogger() 61 | with fixture: 62 | logging.info("two") 63 | logging.info("three") 64 | self.assertEqual("two\n", fixture.output) 65 | self.assertEqual("one\nthree\n", stream.getvalue()) 66 | 67 | def test_preserving_existing_handlers(self): 68 | stream = io.StringIO() 69 | self.logger.addHandler(logging.StreamHandler(stream)) 70 | self.logger.setLevel(logging.INFO) 71 | fixture = FakeLogger(nuke_handlers=False) 72 | with fixture: 73 | logging.info("message") 74 | self.assertEqual("message\n", fixture.output) 75 | self.assertEqual("message\n", stream.getvalue()) 76 | 77 | def test_logging_level_restored(self): 78 | self.logger.setLevel(logging.DEBUG) 79 | fixture = FakeLogger(level=logging.WARNING) 80 | with fixture: 81 | # The fixture won't capture this, because the DEBUG level 82 | # is lower than the WARNING one 83 | logging.debug("debug message") 84 | self.assertEqual(logging.WARNING, self.logger.level) 85 | self.assertEqual("", fixture.output) 86 | self.assertEqual(logging.DEBUG, self.logger.level) 87 | 88 | def test_custom_format(self): 89 | fixture = FakeLogger(format="%(module)s") 90 | self.useFixture(fixture) 91 | logging.info("message") 92 | self.assertEqual("test_logger\n", fixture.output) 93 | 94 | def test_custom_datefmt(self): 95 | fixture = FakeLogger(format="%(asctime)s %(module)s", datefmt="%Y") 96 | self.useFixture(fixture) 97 | logging.info("message") 98 | self.assertEqual( 99 | time.strftime("%Y test_logger\n", time.localtime()), fixture.output 100 | ) 101 | 102 | def test_custom_formatter(self): 103 | fixture = FakeLogger( 104 | format="%(asctime)s %(module)s", 105 | formatter=FooFormatter, 106 | datefmt="%Y", 107 | ) 108 | self.useFixture(fixture) 109 | logging.info("message") 110 | self.assertEqual( 111 | time.strftime("Foo %Y test_logger\n", time.localtime()), 112 | fixture.output, 113 | ) 114 | 115 | def test_logging_output_included_in_details(self): 116 | fixture = FakeLogger() 117 | detail_name = "pythonlogging:''" 118 | with fixture: 119 | content = fixture.getDetails()[detail_name] 120 | # Output after getDetails is called is included. 121 | logging.info("some message") 122 | self.assertEqual("some message\n", content.as_text()) 123 | # The old content object returns the old usage after cleanUp (not 124 | # strictly needed but convenient). Note that no guarantee is made that 125 | # it will work after setUp is called again. [It does on Python 2.x, not 126 | # on 3.x] 127 | self.assertEqual("some message\n", content.as_text()) 128 | with fixture: 129 | # A new one returns new output: 130 | self.assertEqual("", fixture.getDetails()[detail_name].as_text()) 131 | # The original content object may either fail, or return the old 132 | # content (it must not have been reset..). 133 | try: 134 | self.assertEqual("some message\n", content.as_text()) 135 | except AssertionError: 136 | raise 137 | except BaseException: 138 | pass 139 | 140 | def test_exceptionraised(self): 141 | with FakeLogger(): 142 | with testtools.ExpectedException(TypeError): 143 | logging.info("Some message", "wrongarg") 144 | 145 | def test_output_can_be_reset(self): 146 | fixture = FakeLogger() 147 | with fixture: 148 | logging.info("message") 149 | 150 | fixture.reset_output() 151 | 152 | self.assertEqual("", fixture.output) 153 | 154 | 155 | class LogHandlerTest(TestCase, TestWithFixtures): 156 | class CustomHandler(logging.Handler): 157 | def __init__(self, *args, **kwargs): 158 | """Create the instance, and add a records attribute.""" 159 | logging.Handler.__init__(self, *args, **kwargs) 160 | self.msgs = [] 161 | 162 | def emit(self, record): 163 | self.msgs.append(record.msg) 164 | 165 | def setUp(self): 166 | super(LogHandlerTest, self).setUp() 167 | self.logger = logging.getLogger() 168 | self.addCleanup(self.removeHandlers, self.logger) 169 | 170 | def removeHandlers(self, logger): 171 | for handler in logger.handlers: 172 | logger.removeHandler(handler) 173 | 174 | def test_captures_logging(self): 175 | fixture = self.useFixture(LogHandler(self.CustomHandler())) 176 | logging.info("some message") 177 | self.assertEqual(["some message"], fixture.handler.msgs) 178 | 179 | def test_replace_and_restore_handlers(self): 180 | stream = io.StringIO() 181 | logger = logging.getLogger() 182 | logger.addHandler(logging.StreamHandler(stream)) 183 | logger.setLevel(logging.INFO) 184 | logging.info("one") 185 | fixture = LogHandler(self.CustomHandler()) 186 | with fixture: 187 | logging.info("two") 188 | logging.info("three") 189 | self.assertEqual(["two"], fixture.handler.msgs) 190 | self.assertEqual("one\nthree\n", stream.getvalue()) 191 | 192 | def test_preserving_existing_handlers(self): 193 | stream = io.StringIO() 194 | self.logger.addHandler(logging.StreamHandler(stream)) 195 | self.logger.setLevel(logging.INFO) 196 | fixture = LogHandler(self.CustomHandler(), nuke_handlers=False) 197 | with fixture: 198 | logging.info("message") 199 | self.assertEqual(["message"], fixture.handler.msgs) 200 | self.assertEqual("message\n", stream.getvalue()) 201 | 202 | def test_logging_level_restored(self): 203 | self.logger.setLevel(logging.DEBUG) 204 | fixture = LogHandler(self.CustomHandler(), level=logging.WARNING) 205 | with fixture: 206 | # The fixture won't capture this, because the DEBUG level 207 | # is lower than the WARNING one 208 | logging.debug("debug message") 209 | self.assertEqual(logging.WARNING, self.logger.level) 210 | self.assertEqual([], fixture.handler.msgs) 211 | self.assertEqual(logging.DEBUG, self.logger.level) 212 | -------------------------------------------------------------------------------- /fixtures/tests/_fixtures/test_mockpatch.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 IBM Corp. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | 16 | import testtools 17 | from unittest import mock 18 | 19 | from fixtures import ( 20 | MockPatch, 21 | MockPatchMultiple, 22 | MockPatchObject, 23 | ) 24 | 25 | 26 | class Foo(object): 27 | def bar(self): 28 | return self 29 | 30 | 31 | def mocking_bar(self): 32 | return "mocked!" 33 | 34 | 35 | class TestMockPatch(testtools.TestCase): 36 | def test_mock_patch_with_replacement(self): 37 | self.useFixture(MockPatch("%s.Foo.bar" % (__name__), mocking_bar)) 38 | instance = Foo() 39 | self.assertEqual(instance.bar(), "mocked!") 40 | 41 | def test_mock_patch_without_replacement(self): 42 | self.useFixture(MockPatch("%s.Foo.bar" % (__name__))) 43 | instance = Foo() 44 | self.assertIsInstance(instance.bar(), mock.MagicMock) 45 | 46 | 47 | class TestMockMultiple(testtools.TestCase): 48 | def test_mock_multiple_with_replacement(self): 49 | self.useFixture(MockPatchMultiple("%s.Foo" % (__name__), bar=mocking_bar)) 50 | instance = Foo() 51 | self.assertEqual(instance.bar(), "mocked!") 52 | 53 | def test_mock_patch_without_replacement(self): 54 | self.useFixture( 55 | MockPatchMultiple("%s.Foo" % (__name__), bar=MockPatchMultiple.DEFAULT) 56 | ) 57 | instance = Foo() 58 | self.assertIsInstance(instance.bar(), mock.MagicMock) 59 | 60 | 61 | class TestMockPatchObject(testtools.TestCase): 62 | def test_mock_patch_object_with_replacement(self): 63 | self.useFixture(MockPatchObject(Foo, "bar", mocking_bar)) 64 | instance = Foo() 65 | self.assertEqual(instance.bar(), "mocked!") 66 | 67 | def test_mock_patch_object_without_replacement(self): 68 | self.useFixture(MockPatchObject(Foo, "bar")) 69 | instance = Foo() 70 | self.assertIsInstance(instance.bar(), mock.MagicMock) 71 | -------------------------------------------------------------------------------- /fixtures/tests/_fixtures/test_monkeypatch.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | import functools 17 | import sys 18 | 19 | import testtools 20 | from testtools.matchers import Is 21 | 22 | from fixtures import MonkeyPatch, TestWithFixtures 23 | 24 | reference = 23 25 | 26 | NEW_PY39_CLASSMETHOD = sys.version_info[:2] in ( 27 | (3, 9), 28 | (3, 10), 29 | ) and not hasattr(sys, "pypy_version_info") 30 | 31 | 32 | class C(object): 33 | def foo(self, arg): 34 | return arg 35 | 36 | @staticmethod 37 | def foo_static(): 38 | pass 39 | 40 | @classmethod 41 | def foo_cls(cls): 42 | pass 43 | 44 | 45 | class D(object): 46 | def bar(self): 47 | pass 48 | 49 | def bar_two_args(self, arg=None): 50 | return (self, arg) 51 | 52 | @classmethod 53 | def bar_cls(cls): 54 | return cls 55 | 56 | @classmethod 57 | def bar_cls_args(cls, *args): 58 | return (cls,) + args 59 | 60 | @staticmethod 61 | def bar_static(): 62 | pass 63 | 64 | @staticmethod 65 | def bar_static_args(*args): 66 | return args 67 | 68 | def bar_self_referential(self, *args, **kwargs): 69 | self.bar() 70 | 71 | 72 | INST_C = C() 73 | 74 | 75 | def fake(*args): 76 | return args 77 | 78 | 79 | def fake2(arg): 80 | pass 81 | 82 | 83 | def fake_no_args(): 84 | pass 85 | 86 | 87 | def fake_no_args2(): 88 | pass 89 | 90 | 91 | @staticmethod 92 | def fake_static(): 93 | pass 94 | 95 | 96 | class TestMonkeyPatch(testtools.TestCase, TestWithFixtures): 97 | def test_patch_and_restore(self): 98 | fixture = MonkeyPatch("fixtures.tests._fixtures.test_monkeypatch.reference", 45) 99 | self.assertEqual(23, reference) 100 | fixture.setUp() 101 | try: 102 | self.assertEqual(45, reference) 103 | finally: 104 | fixture.cleanUp() 105 | self.assertEqual(23, reference) 106 | 107 | def test_patch_missing_attribute(self): 108 | fixture = MonkeyPatch( 109 | "fixtures.tests._fixtures.test_monkeypatch.new_attr", True 110 | ) 111 | self.assertFalse("new_attr" in globals()) 112 | fixture.setUp() 113 | try: 114 | self.assertEqual(True, new_attr) # noqa: F821 115 | finally: 116 | fixture.cleanUp() 117 | self.assertFalse("new_attr" in globals()) 118 | 119 | def test_delete_existing_attribute(self): 120 | fixture = MonkeyPatch( 121 | "fixtures.tests._fixtures.test_monkeypatch.reference", 122 | MonkeyPatch.delete, 123 | ) 124 | self.assertEqual(23, reference) 125 | fixture.setUp() 126 | try: 127 | self.assertFalse("reference" in globals()) 128 | finally: 129 | fixture.cleanUp() 130 | self.assertEqual(23, reference) 131 | 132 | def test_delete_missing_attribute(self): 133 | fixture = MonkeyPatch( 134 | "fixtures.tests._fixtures.test_monkeypatch.new_attr", 135 | MonkeyPatch.delete, 136 | ) 137 | self.assertFalse("new_attr" in globals()) 138 | fixture.setUp() 139 | try: 140 | self.assertFalse("new_attr" in globals()) 141 | finally: 142 | fixture.cleanUp() 143 | self.assertFalse("new_attr" in globals()) 144 | 145 | def _check_restored_static_or_class_method( 146 | self, oldmethod, oldmethod_inst, klass, methodname 147 | ): 148 | restored_method = getattr(klass, methodname) 149 | restored_method_inst = getattr(klass(), methodname) 150 | self.assertEqual(oldmethod, restored_method) 151 | self.assertEqual(oldmethod, restored_method_inst) 152 | self.assertEqual(oldmethod_inst, restored_method) 153 | self.assertEqual(oldmethod_inst, restored_method_inst) 154 | restored_method() 155 | restored_method_inst() 156 | 157 | def test_patch_staticmethod_with_staticmethod(self): 158 | oldmethod = C.foo_static 159 | oldmethod_inst = C().foo_static 160 | fixture = MonkeyPatch( 161 | "fixtures.tests._fixtures.test_monkeypatch.C.foo_static", 162 | D.bar_static, 163 | ) 164 | with fixture: 165 | C.foo_static() 166 | C().foo_static() 167 | self._check_restored_static_or_class_method( 168 | oldmethod, oldmethod_inst, C, "foo_static" 169 | ) 170 | 171 | def test_patch_staticmethod_with_classmethod(self): 172 | oldmethod = C.foo_static 173 | oldmethod_inst = C().foo_static 174 | fixture = MonkeyPatch( 175 | "fixtures.tests._fixtures.test_monkeypatch.C.foo_static", D.bar_cls 176 | ) 177 | with fixture: 178 | C.foo_static() 179 | C().foo_static() 180 | self._check_restored_static_or_class_method( 181 | oldmethod, oldmethod_inst, C, "foo_static" 182 | ) 183 | 184 | def test_patch_staticmethod_with_function(self): 185 | oldmethod = C.foo_static 186 | oldmethod_inst = C().foo_static 187 | fixture = MonkeyPatch( 188 | "fixtures.tests._fixtures.test_monkeypatch.C.foo_static", 189 | fake_no_args, 190 | ) 191 | with fixture: 192 | C.foo_static() 193 | C().foo_static() 194 | self._check_restored_static_or_class_method( 195 | oldmethod, oldmethod_inst, C, "foo_static" 196 | ) 197 | 198 | def test_patch_staticmethod_with_boundmethod(self): 199 | oldmethod = C.foo_static 200 | oldmethod_inst = C().foo_static 201 | fixture = MonkeyPatch( 202 | "fixtures.tests._fixtures.test_monkeypatch.C.foo_static", D().bar 203 | ) 204 | with fixture: 205 | C.foo_static() 206 | C().foo_static() 207 | self._check_restored_static_or_class_method( 208 | oldmethod, oldmethod_inst, C, "foo_static" 209 | ) 210 | 211 | def test_patch_classmethod_with_staticmethod(self): 212 | oldmethod = C.foo_cls 213 | oldmethod_inst = C().foo_cls 214 | fixture = MonkeyPatch( 215 | "fixtures.tests._fixtures.test_monkeypatch.C.foo_cls", 216 | D.bar_static_args, 217 | ) 218 | with fixture: 219 | (cls,) = C.foo_cls() 220 | self.expectThat(cls, Is(C)) 221 | (cls,) = C().foo_cls() 222 | self.expectThat(cls, Is(C)) 223 | self._check_restored_static_or_class_method( 224 | oldmethod, oldmethod_inst, C, "foo_cls" 225 | ) 226 | 227 | def test_patch_classmethod_with_classmethod(self): 228 | oldmethod = C.foo_cls 229 | oldmethod_inst = C().foo_cls 230 | fixture = MonkeyPatch( 231 | "fixtures.tests._fixtures.test_monkeypatch.C.foo_cls", 232 | D.bar_cls_args, 233 | ) 234 | with fixture: 235 | # Python 3.9 changes the behavior of the classmethod decorator so 236 | # that it now honours the descriptor binding protocol [1]. 237 | # This means we're now going to call the monkeypatched classmethod 238 | # the way we would have if it hadn't been monkeypatched: simply 239 | # with the class 240 | # 241 | # https://bugs.python.org/issue19072 242 | if NEW_PY39_CLASSMETHOD: 243 | (cls,) = C.foo_cls() 244 | self.expectThat(cls, Is(D)) 245 | (cls,) = C().foo_cls() 246 | self.expectThat(cls, Is(D)) 247 | else: 248 | cls, target_class = C.foo_cls() 249 | self.expectThat(cls, Is(D)) 250 | self.expectThat(target_class, Is(C)) 251 | cls, target_class = C().foo_cls() 252 | self.expectThat(cls, Is(D)) 253 | self.expectThat(target_class, Is(C)) 254 | self._check_restored_static_or_class_method( 255 | oldmethod, oldmethod_inst, C, "foo_cls" 256 | ) 257 | 258 | def test_patch_classmethod_with_function(self): 259 | oldmethod = C.foo_cls 260 | oldmethod_inst = C().foo_cls 261 | fixture = MonkeyPatch( 262 | "fixtures.tests._fixtures.test_monkeypatch.C.foo_cls", fake 263 | ) 264 | with fixture: 265 | (cls,) = C.foo_cls() 266 | self.expectThat(cls, Is(C)) 267 | (cls, arg) = C().foo_cls(1) 268 | self.expectThat(cls, Is(C)) 269 | self.assertEqual(1, arg) 270 | self._check_restored_static_or_class_method( 271 | oldmethod, oldmethod_inst, C, "foo_cls" 272 | ) 273 | 274 | def test_patch_classmethod_with_boundmethod(self): 275 | oldmethod = C.foo_cls 276 | oldmethod_inst = C().foo_cls 277 | d = D() 278 | fixture = MonkeyPatch( 279 | "fixtures.tests._fixtures.test_monkeypatch.C.foo_cls", 280 | d.bar_two_args, 281 | ) 282 | with fixture: 283 | slf, cls = C.foo_cls() 284 | self.expectThat(slf, Is(d)) 285 | # See note in test_patch_classmethod_with_classmethod on changes in 286 | # Python 3.9 287 | if NEW_PY39_CLASSMETHOD: 288 | self.expectThat(cls, Is(None)) 289 | else: 290 | self.expectThat(cls, Is(C)) 291 | slf, cls = C().foo_cls() 292 | self.expectThat(slf, Is(d)) 293 | if NEW_PY39_CLASSMETHOD: 294 | self.expectThat(cls, Is(None)) 295 | else: 296 | self.expectThat(cls, Is(C)) 297 | self._check_restored_static_or_class_method( 298 | oldmethod, oldmethod_inst, C, "foo_cls" 299 | ) 300 | 301 | def test_patch_function_with_staticmethod(self): 302 | oldmethod = fake_no_args 303 | fixture = MonkeyPatch( 304 | "fixtures.tests._fixtures.test_monkeypatch.fake_no_args", 305 | D.bar_static, 306 | ) 307 | with fixture: 308 | fake_no_args() 309 | self.assertEqual(oldmethod, fake_no_args) 310 | 311 | def test_patch_function_with_classmethod(self): 312 | oldmethod = fake_no_args 313 | fixture = MonkeyPatch( 314 | "fixtures.tests._fixtures.test_monkeypatch.fake_no_args", D.bar_cls 315 | ) 316 | with fixture: 317 | fake_no_args() 318 | self.assertEqual(oldmethod, fake_no_args) 319 | 320 | def test_patch_function_with_function(self): 321 | oldmethod = fake_no_args 322 | fixture = MonkeyPatch( 323 | "fixtures.tests._fixtures.test_monkeypatch.fake_no_args", 324 | fake_no_args2, 325 | ) 326 | with fixture: 327 | fake_no_args() 328 | self.assertEqual(oldmethod, fake_no_args) 329 | 330 | def test_patch_function_with_partial(self): 331 | oldmethod = fake_no_args 332 | fixture = MonkeyPatch( 333 | "fixtures.tests._fixtures.test_monkeypatch.fake_no_args", 334 | functools.partial(fake, 1), 335 | ) 336 | with fixture: 337 | (ret,) = fake_no_args() 338 | self.assertEqual(1, ret) 339 | self.assertEqual(oldmethod, fake_no_args) 340 | 341 | def test_patch_function_with_boundmethod(self): 342 | oldmethod = fake_no_args 343 | fixture = MonkeyPatch( 344 | "fixtures.tests._fixtures.test_monkeypatch.fake_no_args", D().bar 345 | ) 346 | with fixture: 347 | fake_no_args() 348 | self.assertEqual(oldmethod, fake_no_args) 349 | 350 | def test_patch_boundmethod_with_staticmethod(self): 351 | oldmethod = INST_C.foo 352 | fixture = MonkeyPatch( 353 | "fixtures.tests._fixtures.test_monkeypatch.INST_C.foo", 354 | D.bar_static, 355 | ) 356 | with fixture: 357 | INST_C.foo() 358 | self.assertEqual(oldmethod, INST_C.foo) 359 | 360 | def test_patch_boundmethod_with_classmethod(self): 361 | oldmethod = INST_C.foo 362 | fixture = MonkeyPatch( 363 | "fixtures.tests._fixtures.test_monkeypatch.INST_C.foo", D.bar_cls 364 | ) 365 | with fixture: 366 | INST_C.foo() 367 | self.assertEqual(oldmethod, INST_C.foo) 368 | 369 | def test_patch_boundmethod_with_function(self): 370 | oldmethod = INST_C.foo 371 | fixture = MonkeyPatch( 372 | "fixtures.tests._fixtures.test_monkeypatch.INST_C.foo", 373 | fake_no_args, 374 | ) 375 | with fixture: 376 | INST_C.foo() 377 | self.assertEqual(oldmethod, INST_C.foo) 378 | 379 | def test_patch_boundmethod_with_boundmethod(self): 380 | oldmethod = INST_C.foo 381 | fixture = MonkeyPatch( 382 | "fixtures.tests._fixtures.test_monkeypatch.INST_C.foo", D().bar 383 | ) 384 | with fixture: 385 | INST_C.foo() 386 | self.assertEqual(oldmethod, INST_C.foo) 387 | sentinel = object() 388 | self.assertEqual(sentinel, INST_C.__dict__.get("foo", sentinel)) 389 | 390 | def test_patch_unboundmethod_with_staticmethod(self): 391 | oldmethod = C.foo 392 | fixture = MonkeyPatch( 393 | "fixtures.tests._fixtures.test_monkeypatch.C.foo", 394 | D.bar_static_args, 395 | ) 396 | with fixture: 397 | target_self, arg = INST_C.foo(1) 398 | self.expectThat(target_self, Is(INST_C)) 399 | self.assertEqual(1, arg) 400 | self.assertEqual(oldmethod, C.foo) 401 | 402 | def test_patch_unboundmethod_with_classmethod(self): 403 | oldmethod = C.foo 404 | fixture = MonkeyPatch( 405 | "fixtures.tests._fixtures.test_monkeypatch.C.foo", D.bar_cls_args 406 | ) 407 | with fixture: 408 | c = C() 409 | cls, target_self, arg = c.foo(1) 410 | self.expectThat(cls, Is(D)) 411 | self.expectThat(target_self, Is(c)) 412 | self.assertEqual(1, arg) 413 | self.assertEqual(oldmethod, C.foo) 414 | 415 | def test_patch_unboundmethod_with_function(self): 416 | oldmethod = C.foo 417 | fixture = MonkeyPatch("fixtures.tests._fixtures.test_monkeypatch.C.foo", fake) 418 | with fixture: 419 | c = C() 420 | target_self, arg = c.foo(1) 421 | self.expectThat(target_self, Is(c)) 422 | self.assertTrue(1, arg) 423 | self.assertEqual(oldmethod, C.foo) 424 | 425 | def test_patch_unboundmethod_with_boundmethod(self): 426 | oldmethod = C.foo 427 | d = D() 428 | fixture = MonkeyPatch( 429 | "fixtures.tests._fixtures.test_monkeypatch.C.foo", d.bar_two_args 430 | ) 431 | with fixture: 432 | c = C() 433 | slf, target_self = c.foo() 434 | self.expectThat(slf, Is(d)) 435 | self.expectThat(target_self, Is(c)) 436 | self.assertEqual(oldmethod, C.foo) 437 | 438 | def test_double_patch_instancemethod(self): 439 | oldmethod = C.foo 440 | oldmethod_inst = C().foo 441 | fixture1 = MonkeyPatch("fixtures.tests._fixtures.test_monkeypatch.C.foo", fake) 442 | fixture2 = MonkeyPatch("fixtures.tests._fixtures.test_monkeypatch.C.foo", fake2) 443 | with fixture1: 444 | with fixture2: 445 | C().foo() 446 | self.assertEqual(oldmethod, C.foo) 447 | # The method address changes with each instantiation of C, and method 448 | # equivalence just tests that. Compare the code objects instead. 449 | self.assertEqual(oldmethod_inst.__code__, C().foo.__code__) 450 | 451 | def test_double_patch_staticmethod(self): 452 | oldmethod = C.foo_static 453 | oldmethod_inst = C().foo_static 454 | fixture1 = MonkeyPatch( 455 | "fixtures.tests._fixtures.test_monkeypatch.C.foo_static", 456 | fake_no_args, 457 | ) 458 | fixture2 = MonkeyPatch( 459 | "fixtures.tests._fixtures.test_monkeypatch.C.foo_static", 460 | fake_static, 461 | ) 462 | with fixture1: 463 | with fixture2: 464 | C.foo_static() 465 | C().foo_static() 466 | self._check_restored_static_or_class_method( 467 | oldmethod, oldmethod_inst, C, "foo_static" 468 | ) 469 | 470 | def test_double_patch_classmethod(self): 471 | oldmethod = C.foo_cls 472 | oldmethod_inst = C().foo_cls 473 | fixture1 = MonkeyPatch( 474 | "fixtures.tests._fixtures.test_monkeypatch.C.foo_cls", fake 475 | ) 476 | fixture2 = MonkeyPatch( 477 | "fixtures.tests._fixtures.test_monkeypatch.C.foo_cls", fake2 478 | ) 479 | with fixture1: 480 | with fixture2: 481 | C.foo_cls() 482 | C().foo_cls() 483 | self._check_restored_static_or_class_method( 484 | oldmethod, oldmethod_inst, C, "foo_cls" 485 | ) 486 | 487 | def test_patch_c_foo_with_instance_d_bar_self_referential(self): 488 | oldmethod = C.foo 489 | oldmethod_inst = C().foo 490 | fixture = MonkeyPatch( 491 | "fixtures.tests._fixtures.test_monkeypatch.C.foo", 492 | D().bar_self_referential, 493 | ) 494 | with fixture: 495 | C().foo() 496 | self.assertEqual(oldmethod, C.foo) 497 | # The method address changes with each instantiation of C, and method 498 | # equivalence just tests that. Compare the code objects instead. 499 | self.assertEqual(oldmethod_inst.__code__, C().foo.__code__) 500 | -------------------------------------------------------------------------------- /fixtures/tests/_fixtures/test_packagepath.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2011, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | import testtools 17 | 18 | import fixtures 19 | from fixtures import ( 20 | PackagePathEntry, 21 | TempDir, 22 | ) 23 | 24 | 25 | class TestPackagePathEntry(testtools.TestCase): 26 | def test_adds_missing_to_end_package_path(self): 27 | uniquedir = self.useFixture(TempDir()).path 28 | fixture = PackagePathEntry("fixtures", uniquedir) 29 | self.assertFalse(uniquedir in fixtures.__path__) 30 | with fixture: 31 | self.assertTrue(uniquedir in fixtures.__path__) 32 | self.assertFalse(uniquedir in fixtures.__path__) 33 | 34 | def test_doesnt_alter_existing_entry(self): 35 | existingdir = fixtures.__path__[0] 36 | expectedlen = len(fixtures.__path__) 37 | fixture = PackagePathEntry("fixtures", existingdir) 38 | with fixture: 39 | self.assertTrue(existingdir in fixtures.__path__) 40 | self.assertEqual(expectedlen, len(fixtures.__path__)) 41 | self.assertTrue(existingdir in fixtures.__path__) 42 | self.assertEqual(expectedlen, len(fixtures.__path__)) 43 | -------------------------------------------------------------------------------- /fixtures/tests/_fixtures/test_popen.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, 2011, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | import inspect 17 | import io 18 | import subprocess 19 | import sys 20 | 21 | import testtools 22 | 23 | from fixtures import FakePopen, TestWithFixtures 24 | from fixtures._fixtures.popen import FakeProcess 25 | 26 | 27 | class TestFakePopen(testtools.TestCase, TestWithFixtures): 28 | def test_installs_restores_global(self): 29 | fixture = FakePopen() 30 | popen = subprocess.Popen 31 | fixture.setUp() 32 | try: 33 | self.assertEqual(subprocess.Popen, fixture) 34 | finally: 35 | fixture.cleanUp() 36 | self.assertEqual(subprocess.Popen, popen) 37 | 38 | def test___call___is_recorded(self): 39 | fixture = self.useFixture(FakePopen()) 40 | proc = fixture(["foo", "bar"], 1, None, "in", "out", "err") 41 | self.assertEqual(1, len(fixture.procs)) 42 | self.assertEqual( 43 | dict( 44 | args=["foo", "bar"], 45 | bufsize=1, 46 | executable=None, 47 | stdin="in", 48 | stdout="out", 49 | stderr="err", 50 | ), 51 | proc._args, 52 | ) 53 | 54 | def test_inject_content_stdout(self): 55 | def get_info(args): 56 | return {"stdout": "stdout"} 57 | 58 | fixture = self.useFixture(FakePopen(get_info)) 59 | proc = fixture(["foo"]) 60 | self.assertEqual("stdout", proc.stdout) 61 | 62 | def test_handles_all_Popen_args(self): 63 | all_args = dict( 64 | args="args", 65 | bufsize="bufsize", 66 | executable="executable", 67 | stdin="stdin", 68 | stdout="stdout", 69 | stderr="stderr", 70 | preexec_fn="preexec_fn", 71 | close_fds="close_fds", 72 | shell="shell", 73 | cwd="cwd", 74 | env="env", 75 | universal_newlines="universal_newlines", 76 | startupinfo="startupinfo", 77 | creationflags="creationflags", 78 | restore_signals="restore_signals", 79 | start_new_session="start_new_session", 80 | pass_fds="pass_fds", 81 | encoding="encoding", 82 | errors="errors", 83 | text="text", 84 | ) 85 | if sys.version_info >= (3, 9): 86 | all_args["group"] = "group" 87 | all_args["extra_groups"] = "extra_groups" 88 | all_args["user"] = "user" 89 | all_args["umask"] = "umask" 90 | if sys.version_info >= (3, 10): 91 | all_args["pipesize"] = "pipesize" 92 | if sys.version_info >= (3, 11): 93 | all_args["process_group"] = "process_group" 94 | 95 | def get_info(proc_args): 96 | self.assertEqual(all_args, proc_args) 97 | return {} 98 | 99 | fixture = self.useFixture(FakePopen(get_info)) 100 | fixture(**all_args) 101 | 102 | @testtools.skipUnless(sys.version_info < (3, 9), "only relevant on Python <3.9") 103 | def test_rejects_3_9_args_on_older_versions(self): 104 | fixture = self.useFixture(FakePopen(lambda proc_args: {})) 105 | for arg_name in ("group", "extra_groups", "user", "umask"): 106 | kwargs = {arg_name: arg_name} 107 | expected_message = r".* got an unexpected keyword argument '{}'".format( 108 | arg_name 109 | ) 110 | with testtools.ExpectedException(TypeError, expected_message): 111 | fixture(args="args", **kwargs) 112 | 113 | @testtools.skipUnless(sys.version_info < (3, 10), "only relevant on Python <3.10") 114 | def test_rejects_3_10_args_on_older_versions(self): 115 | fixture = self.useFixture(FakePopen(lambda proc_args: {})) 116 | with testtools.ExpectedException( 117 | TypeError, r".* got an unexpected keyword argument 'pipesize'" 118 | ): 119 | fixture(args="args", pipesize=1024) 120 | 121 | @testtools.skipUnless(sys.version_info < (3, 11), "only relevant on Python <3.11") 122 | def test_rejects_3_11_args_on_older_versions(self): 123 | fixture = self.useFixture(FakePopen(lambda proc_args: {})) 124 | with testtools.ExpectedException( 125 | TypeError, r".* got an unexpected keyword argument 'process_group'" 126 | ): 127 | fixture(args="args", process_group=42) 128 | 129 | def test_function_signature(self): 130 | fake_signature = inspect.getfullargspec(FakePopen.__call__) 131 | real_signature = inspect.getfullargspec(subprocess.Popen) 132 | 133 | # compare args 134 | 135 | fake_args = fake_signature.args 136 | real_args = real_signature.args 137 | 138 | self.assertListEqual( 139 | fake_args, 140 | real_args, 141 | "Function signature of FakePopen doesn't match subprocess.Popen", 142 | ) 143 | 144 | # compare kw-only args; order doesn't matter so we use a set 145 | 146 | fake_kwargs = set(fake_signature.kwonlyargs) 147 | real_kwargs = set(real_signature.kwonlyargs) 148 | 149 | if sys.version_info < (3, 11): 150 | fake_kwargs.remove("process_group") 151 | 152 | if sys.version_info < (3, 10): 153 | fake_kwargs.remove("pipesize") 154 | 155 | if sys.version_info < (3, 9): 156 | fake_kwargs.remove("group") 157 | fake_kwargs.remove("extra_groups") 158 | fake_kwargs.remove("user") 159 | fake_kwargs.remove("umask") 160 | 161 | self.assertSetEqual( 162 | fake_kwargs, 163 | real_kwargs, 164 | "Function signature of FakePopen doesn't match subprocess.Popen", 165 | ) 166 | 167 | def test_custom_returncode(self): 168 | def get_info(proc_args): 169 | return dict(returncode=1) 170 | 171 | proc = self.useFixture(FakePopen(get_info))(["foo"]) 172 | self.assertEqual(None, proc.returncode) 173 | self.assertEqual(1, proc.wait()) 174 | self.assertEqual(1, proc.returncode) 175 | 176 | def test_with_popen_custom(self): 177 | self.useFixture(FakePopen()) 178 | with subprocess.Popen(["ls -lh"]) as proc: 179 | self.assertEqual(None, proc.returncode) 180 | self.assertEqual(["ls -lh"], proc.args) 181 | 182 | 183 | class TestFakeProcess(testtools.TestCase): 184 | def test_wait(self): 185 | proc = FakeProcess({}, {}) 186 | proc.returncode = 45 187 | self.assertEqual(45, proc.wait()) 188 | 189 | def test_communicate(self): 190 | proc = FakeProcess({}, {}) 191 | self.assertEqual(("", ""), proc.communicate()) 192 | self.assertEqual(0, proc.returncode) 193 | 194 | def test_communicate_with_out(self): 195 | proc = FakeProcess({}, {"stdout": io.BytesIO(b"foo")}) 196 | self.assertEqual((b"foo", ""), proc.communicate()) 197 | self.assertEqual(0, proc.returncode) 198 | 199 | def test_communicate_with_input(self): 200 | proc = FakeProcess({}, {"stdout": io.BytesIO(b"foo")}) 201 | self.assertEqual((b"foo", ""), proc.communicate(input=b"bar")) 202 | 203 | def test_communicate_with_input_and_stdin(self): 204 | stdin = io.BytesIO() 205 | proc = FakeProcess({}, {"stdin": stdin}) 206 | proc.communicate(input=b"hello") 207 | self.assertEqual(b"hello", stdin.getvalue()) 208 | 209 | def test_communicate_with_timeout(self): 210 | proc = FakeProcess({}, {"stdout": io.BytesIO(b"foo")}) 211 | self.assertEqual((b"foo", ""), proc.communicate(timeout=10)) 212 | 213 | def test_args(self): 214 | proc = FakeProcess({"args": ["ls", "-lh"]}, {}) 215 | proc.returncode = 45 216 | self.assertEqual(45, proc.wait()) 217 | self.assertEqual(proc.args, ["ls", "-lh"]) 218 | 219 | def test_kill(self): 220 | proc = FakeProcess({}, {}) 221 | self.assertIs(None, proc.kill()) 222 | 223 | def test_poll(self): 224 | proc = FakeProcess({}, {}) 225 | self.assertIs(None, proc.poll()) 226 | proc.communicate() 227 | self.assertEqual(0, proc.poll()) 228 | 229 | def test_poll_with_returncode(self): 230 | proc = FakeProcess({}, {}) 231 | proc.communicate() 232 | self.assertEqual(0, proc.poll()) 233 | 234 | def test_wait_with_timeout_and_endtime(self): 235 | proc = FakeProcess({}, {}) 236 | self.assertEqual(0, proc.wait(timeout=4, endtime=7)) 237 | -------------------------------------------------------------------------------- /fixtures/tests/_fixtures/test_pythonpackage.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | import os.path 17 | 18 | import testtools 19 | 20 | from fixtures import PythonPackage, TestWithFixtures 21 | 22 | 23 | class TestPythonPackage(testtools.TestCase, TestWithFixtures): 24 | def test_has_tempdir(self): 25 | fixture = PythonPackage("foo", []) 26 | fixture.setUp() 27 | try: 28 | self.assertTrue(os.path.isdir(fixture.base)) 29 | finally: 30 | fixture.cleanUp() 31 | 32 | def test_writes_package(self): 33 | fixture = PythonPackage("foo", [("bar.py", b"woo")]) 34 | fixture.setUp() 35 | try: 36 | self.assertEqual( 37 | "", 38 | open(os.path.join(fixture.base, "foo", "__init__.py")).read(), 39 | ) 40 | self.assertEqual( 41 | "woo", open(os.path.join(fixture.base, "foo", "bar.py")).read() 42 | ) 43 | finally: 44 | fixture.cleanUp() 45 | 46 | def test_no__init__(self): 47 | fixture = PythonPackage("foo", [("bar.py", b"woo")], init=False) 48 | fixture.setUp() 49 | try: 50 | self.assertFalse( 51 | os.path.exists(os.path.join(fixture.base, "foo", "__init__.py")) 52 | ) 53 | finally: 54 | fixture.cleanUp() 55 | -------------------------------------------------------------------------------- /fixtures/tests/_fixtures/test_pythonpath.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2011, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | import sys 17 | 18 | import testtools 19 | 20 | from fixtures import ( 21 | PythonPathEntry, 22 | TempDir, 23 | ) 24 | 25 | 26 | class TestPythonPathEntry(testtools.TestCase): 27 | def test_adds_missing_to_end_sys_path(self): 28 | uniquedir = self.useFixture(TempDir()).path 29 | fixture = PythonPathEntry(uniquedir) 30 | self.assertFalse(uniquedir in sys.path) 31 | with fixture: 32 | self.assertTrue(uniquedir in sys.path) 33 | self.assertFalse(uniquedir in sys.path) 34 | 35 | def test_doesnt_alter_existing_entry(self): 36 | existingdir = sys.path[0] 37 | expectedlen = len(sys.path) 38 | fixture = PythonPathEntry(existingdir) 39 | with fixture: 40 | self.assertTrue(existingdir in sys.path) 41 | self.assertEqual(expectedlen, len(sys.path)) 42 | self.assertTrue(existingdir in sys.path) 43 | self.assertEqual(expectedlen, len(sys.path)) 44 | -------------------------------------------------------------------------------- /fixtures/tests/_fixtures/test_streams.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2012, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | from testtools import TestCase 17 | from testtools.matchers import Contains 18 | 19 | from fixtures import ( 20 | ByteStream, 21 | DetailStream, 22 | StringStream, 23 | ) 24 | 25 | 26 | class DetailStreamTest(TestCase): 27 | def test_doc_mentions_deprecated(self): 28 | self.assertThat(DetailStream.__doc__, Contains("Deprecated")) 29 | 30 | 31 | class TestByteStreams(TestCase): 32 | def test_empty_detail_stream(self): 33 | detail_name = "test" 34 | fixture = ByteStream(detail_name) 35 | with fixture: 36 | content = fixture.getDetails()[detail_name] 37 | self.assertEqual("", content.as_text()) 38 | 39 | def test_stream_content_in_details(self): 40 | detail_name = "test" 41 | fixture = ByteStream(detail_name) 42 | with fixture: 43 | stream = fixture.stream 44 | content = fixture.getDetails()[detail_name] 45 | # Output after getDetails is called is included. 46 | stream.write(b"testing 1 2 3") 47 | self.assertEqual("testing 1 2 3", content.as_text()) 48 | 49 | def test_stream_content_reset(self): 50 | detail_name = "test" 51 | fixture = ByteStream(detail_name) 52 | with fixture: 53 | stream = fixture.stream 54 | content = fixture.getDetails()[detail_name] 55 | stream.write(b"testing 1 2 3") 56 | with fixture: 57 | # The old content object returns the old usage 58 | self.assertEqual("testing 1 2 3", content.as_text()) 59 | content = fixture.getDetails()[detail_name] 60 | # A new fixture returns the new output: 61 | stream = fixture.stream 62 | stream.write(b"1 2 3 testing") 63 | self.assertEqual("1 2 3 testing", content.as_text()) 64 | 65 | 66 | class TestStringStreams(TestCase): 67 | def test_empty_detail_stream(self): 68 | detail_name = "test" 69 | fixture = StringStream(detail_name) 70 | with fixture: 71 | content = fixture.getDetails()[detail_name] 72 | self.assertEqual("", content.as_text()) 73 | 74 | def test_stream_content_in_details(self): 75 | detail_name = "test" 76 | fixture = StringStream(detail_name) 77 | with fixture: 78 | stream = fixture.stream 79 | content = fixture.getDetails()[detail_name] 80 | # Output after getDetails is called is included. 81 | stream.write("testing 1 2 3") 82 | self.assertEqual("testing 1 2 3", content.as_text()) 83 | 84 | def test_stream_content_reset(self): 85 | detail_name = "test" 86 | fixture = StringStream(detail_name) 87 | with fixture: 88 | stream = fixture.stream 89 | content = fixture.getDetails()[detail_name] 90 | stream.write("testing 1 2 3") 91 | with fixture: 92 | # The old content object returns the old usage 93 | self.assertEqual("testing 1 2 3", content.as_text()) 94 | content = fixture.getDetails()[detail_name] 95 | # A new fixture returns the new output: 96 | stream = fixture.stream 97 | stream.write("1 2 3 testing") 98 | self.assertEqual("1 2 3 testing", content.as_text()) 99 | -------------------------------------------------------------------------------- /fixtures/tests/_fixtures/test_tempdir.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | import os 17 | import tempfile 18 | 19 | import testtools 20 | from testtools.matchers import StartsWith 21 | 22 | from fixtures import ( 23 | NestedTempfile, 24 | TempDir, 25 | ) 26 | 27 | 28 | class TestTempDir(testtools.TestCase): 29 | def test_basic(self): 30 | fixture = TempDir() 31 | sentinel = object() 32 | self.assertEqual(sentinel, getattr(fixture, "path", sentinel)) 33 | fixture.setUp() 34 | try: 35 | path = fixture.path 36 | self.assertTrue(os.path.isdir(path)) 37 | finally: 38 | fixture.cleanUp() 39 | self.assertFalse(os.path.isdir(path)) 40 | 41 | def test_under_dir(self): 42 | root = self.useFixture(TempDir()).path 43 | fixture = TempDir(root) 44 | fixture.setUp() 45 | with fixture: 46 | self.assertThat(fixture.path, StartsWith(root)) 47 | 48 | def test_join(self): 49 | temp_dir = self.useFixture(TempDir()) 50 | root = temp_dir.path 51 | relpath = "foo/bar/baz" 52 | self.assertEqual(os.path.join(root, relpath), temp_dir.join(relpath)) 53 | 54 | def test_join_multiple_children(self): 55 | temp_dir = self.useFixture(TempDir()) 56 | root = temp_dir.path 57 | self.assertEqual( 58 | os.path.join(root, "foo", "bar", "baz"), 59 | temp_dir.join("foo", "bar", "baz"), 60 | ) 61 | 62 | def test_join_naughty_children(self): 63 | temp_dir = self.useFixture(TempDir()) 64 | root = temp_dir.path 65 | self.assertEqual( 66 | os.path.abspath(os.path.join(root, "..", "bar", "baz")), 67 | temp_dir.join("..", "bar", "baz"), 68 | ) 69 | 70 | 71 | class NestedTempfileTest(testtools.TestCase): 72 | """Tests for `NestedTempfile`.""" 73 | 74 | def test_normal(self): 75 | # The temp directory is removed when the context is exited. 76 | starting_tempdir = tempfile.gettempdir() 77 | with NestedTempfile(): 78 | self.assertEqual(tempfile.tempdir, tempfile.gettempdir()) 79 | self.assertNotEqual(starting_tempdir, tempfile.tempdir) 80 | self.assertTrue(os.path.isdir(tempfile.tempdir)) 81 | nested_tempdir = tempfile.tempdir 82 | self.assertEqual(tempfile.tempdir, tempfile.gettempdir()) 83 | self.assertEqual(starting_tempdir, tempfile.tempdir) 84 | self.assertFalse(os.path.isdir(nested_tempdir)) 85 | 86 | def test_exception(self): 87 | # The temp directory is removed when the context is exited, even if 88 | # the code running in context raises an exception. 89 | class ContrivedException(Exception): 90 | pass 91 | 92 | try: 93 | with NestedTempfile(): 94 | nested_tempdir = tempfile.tempdir 95 | raise ContrivedException 96 | except ContrivedException: 97 | self.assertFalse(os.path.isdir(nested_tempdir)) 98 | -------------------------------------------------------------------------------- /fixtures/tests/_fixtures/test_temphomedir.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2011 Canonical Ltd. 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | import os 17 | 18 | import testtools 19 | from testtools.matchers import StartsWith 20 | 21 | from fixtures import ( 22 | TempDir, 23 | TempHomeDir, 24 | ) 25 | 26 | 27 | class TestTempDir(testtools.TestCase): 28 | def test_basic(self): 29 | fixture = TempHomeDir() 30 | sentinel = object() 31 | self.assertEqual(sentinel, getattr(fixture, "path", sentinel)) 32 | fixture.setUp() 33 | try: 34 | path = fixture.path 35 | self.assertTrue(os.path.isdir(path)) 36 | self.assertEqual(path, os.environ.get("HOME")) 37 | finally: 38 | fixture.cleanUp() 39 | self.assertFalse(os.path.isdir(path)) 40 | 41 | def test_under_dir(self): 42 | root = self.useFixture(TempDir()).path 43 | fixture = TempHomeDir(root) 44 | fixture.setUp() 45 | with fixture: 46 | self.assertThat(fixture.path, StartsWith(root)) 47 | -------------------------------------------------------------------------------- /fixtures/tests/_fixtures/test_timeout.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (C) 2011, Martin Pool 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | import signal 17 | import time 18 | 19 | import testtools 20 | from testtools.testcase import ( 21 | TestSkipped, 22 | ) 23 | from testtools.matchers import raises 24 | 25 | import fixtures 26 | 27 | 28 | def sample_timeout_passes(): 29 | with fixtures.Timeout(100, gentle=True): 30 | pass # Timeout shouldn't fire 31 | 32 | 33 | def sample_long_delay_with_gentle_timeout(): 34 | with fixtures.Timeout(1, gentle=True): 35 | time.sleep(100) # Expected to be killed here. 36 | 37 | 38 | def sample_long_delay_with_harsh_timeout(): 39 | with fixtures.Timeout(1, gentle=False): 40 | time.sleep(100) # Expected to be killed here. 41 | 42 | 43 | class TestTimeout(testtools.TestCase, fixtures.TestWithFixtures): 44 | def requireUnix(self): 45 | if getattr(signal, "alarm", None) is None: 46 | raise TestSkipped("no alarm() function") 47 | 48 | def test_timeout_passes(self): 49 | # This can pass even on Windows - the test is skipped. 50 | sample_timeout_passes() 51 | 52 | def test_timeout_gentle(self): 53 | self.requireUnix() 54 | self.assertRaises( 55 | fixtures.TimeoutException, sample_long_delay_with_gentle_timeout 56 | ) 57 | 58 | def test_timeout_harsh(self): 59 | self.requireUnix() 60 | 61 | # This will normally kill the whole process, which would be 62 | # inconvenient. Let's hook the alarm here so we can observe it. 63 | class GotAlarm(Exception): 64 | pass 65 | 66 | def sigalrm_handler(signum, frame): 67 | raise GotAlarm() 68 | 69 | old_handler = signal.signal(signal.SIGALRM, sigalrm_handler) 70 | self.addCleanup(signal.signal, signal.SIGALRM, old_handler) 71 | self.assertThat(sample_long_delay_with_harsh_timeout, raises(GotAlarm)) 72 | -------------------------------------------------------------------------------- /fixtures/tests/_fixtures/test_warnings.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 4 | # license at the users choice. A copy of both licenses are available in the 5 | # project source as Apache-2.0 and BSD. You may not use this file except in 6 | # compliance with one of these two licences. 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 10 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 11 | # license you chose for the specific language governing permissions and 12 | # limitations under that license. 13 | 14 | import warnings 15 | 16 | import testtools 17 | 18 | import fixtures 19 | 20 | 21 | class TestWarningsCapture(testtools.TestCase, fixtures.TestWithFixtures): 22 | def test_capture_reuse(self): 23 | # DeprecationWarnings are hidden by default in Python 3.2+, enable them 24 | # https://docs.python.org/3/library/warnings.html#default-warning-filter 25 | self.useFixture(fixtures.WarningsFilter()) 26 | warnings.simplefilter("always") 27 | 28 | w = fixtures.WarningsCapture() 29 | with w: 30 | warnings.warn("test", DeprecationWarning) 31 | self.assertEqual(1, len(w.captures)) 32 | with w: 33 | self.assertEqual([], w.captures) 34 | 35 | def test_capture_message(self): 36 | # DeprecationWarnings are hidden by default in Python 3.2+, enable them 37 | # https://docs.python.org/3/library/warnings.html#default-warning-filter 38 | self.useFixture(fixtures.WarningsFilter()) 39 | warnings.simplefilter("always") 40 | 41 | w = self.useFixture(fixtures.WarningsCapture()) 42 | warnings.warn("hi", DeprecationWarning) 43 | self.assertEqual(1, len(w.captures)) 44 | self.assertEqual("hi", str(w.captures[0].message)) 45 | 46 | def test_capture_category(self): 47 | # DeprecationWarnings are hidden by default in Python 3.2+, enable them 48 | # https://docs.python.org/3/library/warnings.html#default-warning-filter 49 | self.useFixture(fixtures.WarningsFilter()) 50 | warnings.simplefilter("always") 51 | 52 | w = self.useFixture(fixtures.WarningsCapture()) 53 | categories = [ 54 | DeprecationWarning, 55 | Warning, 56 | UserWarning, 57 | SyntaxWarning, 58 | RuntimeWarning, 59 | UnicodeWarning, 60 | FutureWarning, 61 | ] 62 | for category in categories: 63 | warnings.warn("test", category) 64 | self.assertEqual(len(categories), len(w.captures)) 65 | for i, category in enumerate(categories): 66 | c = w.captures[i] 67 | self.assertEqual(category, c.category) 68 | 69 | 70 | class TestWarningsFilter(testtools.TestCase, fixtures.TestWithFixtures): 71 | def test_filter(self): 72 | fixture = fixtures.WarningsFilter( 73 | [ 74 | { 75 | "action": "ignore", 76 | "category": DeprecationWarning, 77 | }, 78 | { 79 | "action": "once", 80 | "category": UserWarning, 81 | }, 82 | ], 83 | ) 84 | self.useFixture(fixture) 85 | with warnings.catch_warnings(record=True) as w: 86 | warnings.warn("deprecated", DeprecationWarning) 87 | warnings.warn("user", UserWarning) 88 | 89 | # only the user warning should be present, and it should only have been 90 | # raised once 91 | self.assertEqual(1, len(w)) 92 | 93 | def test_filters_restored(self): 94 | class CustomWarning(Warning): 95 | pass 96 | 97 | fixture = fixtures.WarningsFilter( 98 | [ 99 | { 100 | "action": "once", 101 | "category": CustomWarning, 102 | }, 103 | ], 104 | ) 105 | 106 | # we copy the filter values rather than a reference to the containing 107 | # list since that can change 108 | old_filters = warnings.filters[:] 109 | 110 | # NOTE: we intentionally do not use 'self.useFixture' since we want to 111 | # teardown the fixture manually here before we exit this test method 112 | with fixture: 113 | new_filters = warnings.filters[:] 114 | self.assertEqual(len(old_filters) + 1, len(new_filters)) 115 | self.assertNotEqual(old_filters, new_filters) 116 | 117 | new_filters = warnings.filters[:] 118 | self.assertEqual(len(old_filters), len(new_filters)) 119 | self.assertEqual(old_filters, new_filters) 120 | -------------------------------------------------------------------------------- /fixtures/tests/helpers.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | import fixtures 17 | 18 | 19 | class LoggingFixture(fixtures.Fixture): 20 | def __init__(self, suffix="", calls=None): 21 | super(LoggingFixture, self).__init__() 22 | if calls is None: 23 | calls = [] 24 | self.calls = calls 25 | self.suffix = suffix 26 | 27 | def setUp(self): 28 | super(LoggingFixture, self).setUp() 29 | self.calls.append("setUp" + self.suffix) 30 | self.addCleanup(self.calls.append, "cleanUp" + self.suffix) 31 | 32 | def reset(self): 33 | self.calls.append("reset" + self.suffix) 34 | -------------------------------------------------------------------------------- /fixtures/tests/test_callmany.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | import types 17 | 18 | import testtools 19 | 20 | from fixtures.callmany import CallMany 21 | 22 | 23 | class TestCallMany(testtools.TestCase): 24 | def test__call__raise_errors_false_callsall_returns_exceptions(self): 25 | calls = [] 26 | 27 | def raise_exception1(): 28 | calls.append("1") 29 | raise Exception("woo") 30 | 31 | def raise_exception2(): 32 | calls.append("2") 33 | raise Exception("woo") 34 | 35 | call = CallMany() 36 | call.push(raise_exception2) 37 | call.push(raise_exception1) 38 | exceptions = call(raise_errors=False) 39 | self.assertEqual(["1", "2"], calls) 40 | # There should be two exceptions 41 | self.assertEqual(2, len(exceptions)) 42 | # They should be a sys.exc_info tuple. 43 | self.assertEqual(3, len(exceptions[0])) 44 | type, value, tb = exceptions[0] 45 | self.assertEqual(Exception, type) 46 | self.assertIsInstance(value, Exception) 47 | self.assertEqual(("woo",), value.args) 48 | self.assertIsInstance(tb, types.TracebackType) 49 | 50 | def test_exit_propagates_exceptions(self): 51 | call = CallMany() 52 | call.__enter__() 53 | self.assertEqual(False, call.__exit__(None, None, None)) 54 | 55 | def test_exit_runs_all_raises_first_exception(self): 56 | calls = [] 57 | 58 | def raise_exception1(): 59 | calls.append("1") 60 | raise Exception("woo") 61 | 62 | def raise_exception2(): 63 | calls.append("2") 64 | raise Exception("hoo") 65 | 66 | call = CallMany() 67 | call.push(raise_exception2) 68 | call.push(raise_exception1) 69 | call.__enter__() 70 | exc = self.assertRaises(Exception, call.__exit__, None, None, None) 71 | self.assertEqual(("woo",), exc.args[0][1].args) 72 | self.assertEqual(("hoo",), exc.args[1][1].args) 73 | self.assertEqual(["1", "2"], calls) 74 | -------------------------------------------------------------------------------- /fixtures/tests/test_fixture.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | import types 17 | 18 | import testtools 19 | from testtools.content import text_content 20 | from testtools.testcase import skipIf 21 | 22 | import fixtures 23 | from fixtures.fixture import gather_details 24 | from fixtures.tests.helpers import LoggingFixture 25 | 26 | 27 | require_gather_details = skipIf( 28 | gather_details is None, "gather_details() is not available." 29 | ) 30 | 31 | 32 | # Note: the cleanup related tests are strictly speaking redundant, IFF they are 33 | # replaced with contract tests for correct use of CallMany. 34 | class TestFixture(testtools.TestCase): 35 | def test_resetCallsSetUpCleanUp(self): 36 | calls = [] 37 | 38 | class FixtureWithSetupOnly(fixtures.Fixture): 39 | def setUp(self): 40 | super(FixtureWithSetupOnly, self).setUp() 41 | calls.append("setUp") 42 | self.addCleanup(calls.append, "cleanUp") 43 | 44 | fixture = FixtureWithSetupOnly() 45 | fixture.setUp() 46 | fixture.reset() 47 | fixture.cleanUp() 48 | self.assertEqual(["setUp", "cleanUp", "setUp", "cleanUp"], calls) 49 | 50 | def test_reset_raises_if_cleanup_raises(self): 51 | class FixtureWithSetupOnly(fixtures.Fixture): 52 | def do_raise(self): 53 | raise Exception("foo") 54 | 55 | def setUp(self): 56 | super(FixtureWithSetupOnly, self).setUp() 57 | self.addCleanup(self.do_raise) 58 | 59 | fixture = FixtureWithSetupOnly() 60 | fixture.setUp() 61 | exc = self.assertRaises(Exception, fixture.reset) 62 | self.assertEqual(("foo",), exc.args) 63 | 64 | def test_cleanUp_raise_first_false_callscleanups_returns_exceptions(self): 65 | calls = [] 66 | 67 | def raise_exception1(): 68 | calls.append("1") 69 | raise Exception("woo") 70 | 71 | def raise_exception2(): 72 | calls.append("2") 73 | raise Exception("woo") 74 | 75 | class FixtureWithException(fixtures.Fixture): 76 | def setUp(self): 77 | super(FixtureWithException, self).setUp() 78 | self.addCleanup(raise_exception2) 79 | self.addCleanup(raise_exception1) 80 | 81 | fixture = FixtureWithException() 82 | fixture.setUp() 83 | exceptions = fixture.cleanUp(raise_first=False) 84 | self.assertEqual(["1", "2"], calls) 85 | # There should be two exceptions 86 | self.assertEqual(2, len(exceptions)) 87 | # They should be a sys.exc_info tuple. 88 | self.assertEqual(3, len(exceptions[0])) 89 | type, value, tb = exceptions[0] 90 | self.assertEqual(Exception, type) 91 | self.assertIsInstance(value, Exception) 92 | self.assertEqual(("woo",), value.args) 93 | self.assertIsInstance(tb, types.TracebackType) 94 | 95 | def test_exit_propagates_exceptions(self): 96 | fixture = fixtures.Fixture() 97 | fixture.__enter__() 98 | self.assertEqual(False, fixture.__exit__(None, None, None)) 99 | 100 | def test_exit_runs_all_raises_first_exception(self): 101 | calls = [] 102 | 103 | def raise_exception1(): 104 | calls.append("1") 105 | raise Exception("woo") 106 | 107 | def raise_exception2(): 108 | calls.append("2") 109 | raise Exception("hoo") 110 | 111 | class FixtureWithException(fixtures.Fixture): 112 | def setUp(self): 113 | super(FixtureWithException, self).setUp() 114 | self.addCleanup(raise_exception2) 115 | self.addCleanup(raise_exception1) 116 | 117 | fixture = FixtureWithException() 118 | fixture.__enter__() 119 | exc = self.assertRaises(Exception, fixture.__exit__, None, None, None) 120 | self.assertEqual(("woo",), exc.args[0][1].args) 121 | self.assertEqual(("hoo",), exc.args[1][1].args) 122 | self.assertEqual(["1", "2"], calls) 123 | 124 | def test_useFixture(self): 125 | parent = LoggingFixture("-outer") 126 | nested = LoggingFixture("-inner", calls=parent.calls) 127 | parent.setUp() 128 | parent.useFixture(nested) 129 | parent.cleanUp() 130 | self.assertEqual( 131 | ["setUp-outer", "setUp-inner", "cleanUp-inner", "cleanUp-outer"], 132 | parent.calls, 133 | ) 134 | 135 | @require_gather_details 136 | def test_useFixture_details_captured_from_setUp(self): 137 | # Details added during fixture set-up are gathered even if setUp() 138 | # fails with an unknown exception. 139 | class SomethingBroke(Exception): 140 | pass 141 | 142 | class BrokenFixture(fixtures.Fixture): 143 | def setUp(self): 144 | super(BrokenFixture, self).setUp() 145 | self.addDetail("content", text_content("foobar")) 146 | raise SomethingBroke() 147 | 148 | broken_fixture = BrokenFixture() 149 | 150 | class SimpleFixture(fixtures.Fixture): 151 | def setUp(self): 152 | super(SimpleFixture, self).setUp() 153 | self.useFixture(broken_fixture) 154 | 155 | simple_fixture = SimpleFixture() 156 | self.assertRaises(SomethingBroke, simple_fixture.setUp) 157 | self.assertEqual( 158 | {"content": text_content("foobar")}, broken_fixture.getDetails() 159 | ) 160 | self.assertEqual( 161 | {"content": text_content("foobar")}, simple_fixture.getDetails() 162 | ) 163 | 164 | @require_gather_details 165 | def test_useFixture_details_captured_from_setUp_MultipleExceptions(self): 166 | # Details added during fixture set-up are gathered even if setUp() 167 | # fails with (cleanly - with MultipleExceptions / SetupError). 168 | class SomethingBroke(Exception): 169 | pass 170 | 171 | class BrokenFixture(fixtures.Fixture): 172 | def _setUp(self): 173 | self.addDetail("content", text_content("foobar")) 174 | raise SomethingBroke() 175 | 176 | class SimpleFixture(fixtures.Fixture): 177 | def _setUp(self): 178 | self.useFixture(BrokenFixture()) 179 | 180 | simple = SimpleFixture() 181 | e = self.assertRaises(fixtures.MultipleExceptions, simple.setUp) 182 | self.assertEqual({"content": text_content("foobar")}, e.args[-1][1].args[0]) 183 | 184 | def test_getDetails(self): 185 | fixture = fixtures.Fixture() 186 | with fixture: 187 | self.assertEqual({}, fixture.getDetails()) 188 | 189 | def test_details_from_child_fixtures_are_returned(self): 190 | parent = fixtures.Fixture() 191 | with parent: 192 | child = fixtures.Fixture() 193 | parent.useFixture(child) 194 | # Note that we add the detail *after* using the fixture: the parent 195 | # has to query just-in-time. 196 | child.addDetail("foo", "content") 197 | self.assertEqual({"foo": "content"}, parent.getDetails()) 198 | # And dropping it from the child drops it from the parent. 199 | del child._details["foo"] 200 | self.assertEqual({}, parent.getDetails()) 201 | # After cleanup the child details are still gone. 202 | child.addDetail("foo", "content") 203 | self.assertRaises(TypeError, parent.getDetails) 204 | 205 | def test_duplicate_details_are_disambiguated(self): 206 | parent = fixtures.Fixture() 207 | with parent: 208 | parent.addDetail("foo", "parent-content") 209 | child = fixtures.Fixture() 210 | parent.useFixture(child) 211 | # Note that we add the detail *after* using the fixture: the parent 212 | # has to query just-in-time. 213 | child.addDetail("foo", "child-content") 214 | self.assertEqual( 215 | { 216 | "foo": "parent-content", 217 | "foo-1": "child-content", 218 | }, 219 | parent.getDetails(), 220 | ) 221 | 222 | def test_addDetail(self): 223 | fixture = fixtures.Fixture() 224 | with fixture: 225 | fixture.addDetail("foo", "content") 226 | self.assertEqual({"foo": "content"}, fixture.getDetails()) 227 | del fixture._details["foo"] 228 | self.assertEqual({}, fixture.getDetails()) 229 | fixture.addDetail("foo", "content") 230 | # Cleanup clears the details too. 231 | self.assertRaises(TypeError, fixture.getDetails) 232 | 233 | def test_setUp_subclassed(self): 234 | # Even though its no longer recommended, we need to be sure that 235 | # overriding setUp and calling super().setUp still works. 236 | class Subclass(fixtures.Fixture): 237 | def setUp(self): 238 | super(Subclass, self).setUp() 239 | self.fred = 1 240 | self.addCleanup(setattr, self, "fred", 2) 241 | 242 | with Subclass() as f: 243 | self.assertEqual(1, f.fred) 244 | self.assertEqual(2, f.fred) 245 | 246 | def test__setUp(self): 247 | # _setUp is called, and cleanups can be registered by it. 248 | class Subclass(fixtures.Fixture): 249 | def _setUp(self): 250 | self.fred = 1 251 | self.addCleanup(setattr, self, "fred", 2) 252 | 253 | with Subclass() as f: 254 | self.assertEqual(1, f.fred) 255 | self.assertEqual(2, f.fred) 256 | 257 | def test__setUp_fails(self): 258 | # when _setUp fails, the fixture is left ready-to-setUp, and any 259 | # details added during _setUp are captured. 260 | class Subclass(fixtures.Fixture): 261 | def _setUp(self): 262 | self.addDetail("log", text_content("stuff")) 263 | 1 / 0 264 | 265 | f = Subclass() 266 | e = self.assertRaises(fixtures.MultipleExceptions, f.setUp) 267 | self.assertRaises(TypeError, f.cleanUp) 268 | self.assertIsInstance(e.args[0][1], ZeroDivisionError) 269 | self.assertIsInstance(e.args[1][1], fixtures.SetupError) 270 | self.assertEqual("stuff", e.args[1][1].args[0]["log"].as_text()) 271 | 272 | def test__setUp_fails_cleanUp_fails(self): 273 | # when _setUp fails, cleanups are called, and their failure is captured 274 | # into the MultipleExceptions instance. 275 | class Subclass(fixtures.Fixture): 276 | def _setUp(self): 277 | self.addDetail("log", text_content("stuff")) 278 | self.addCleanup(lambda: 1 / 0) 279 | raise Exception("fred") 280 | 281 | f = Subclass() 282 | e = self.assertRaises(fixtures.MultipleExceptions, f.setUp) 283 | self.assertRaises(TypeError, f.cleanUp) 284 | self.assertEqual(Exception, e.args[0][0]) 285 | self.assertEqual(ZeroDivisionError, e.args[1][0]) 286 | self.assertEqual(fixtures.SetupError, e.args[2][0]) 287 | self.assertEqual("stuff", e.args[2][1].args[0]["log"].as_text()) 288 | 289 | def test_setup_failures_with_base_exception(self): 290 | # when _setUp fails with a BaseException (or subclass thereof) that 291 | # exception is propagated as is, but we still call cleanups etc. 292 | class MyBase(BaseException): 293 | pass 294 | 295 | log = [] 296 | 297 | class Subclass(fixtures.Fixture): 298 | def _setUp(self): 299 | self.addDetail("log", text_content("stuff")) 300 | self.addCleanup(log.append, "cleaned") 301 | raise MyBase("fred") 302 | 303 | f = Subclass() 304 | self.assertRaises(MyBase, f.setUp) 305 | self.assertRaises(TypeError, f.cleanUp) 306 | self.assertEqual(["cleaned"], log) 307 | 308 | 309 | class TestFunctionFixture(testtools.TestCase): 310 | def test_setup_only(self): 311 | fixture = fixtures.FunctionFixture(lambda: 42) 312 | fixture.setUp() 313 | self.assertEqual(42, fixture.fn_result) 314 | fixture.cleanUp() 315 | self.assertFalse(hasattr(fixture, "fn_result")) 316 | 317 | def test_cleanup(self): 318 | results = [] 319 | fixture = fixtures.FunctionFixture(lambda: 84, results.append) 320 | fixture.setUp() 321 | self.assertEqual(84, fixture.fn_result) 322 | self.assertEqual([], results) 323 | fixture.cleanUp() 324 | self.assertEqual([84], results) 325 | 326 | def test_reset(self): 327 | results = [] 328 | expected = [21, 7] 329 | 330 | def setUp(): 331 | return expected.pop(0) 332 | 333 | def reset(result): 334 | results.append(("reset", result)) 335 | return expected.pop(0) 336 | 337 | fixture = fixtures.FunctionFixture(setUp, results.append, reset) 338 | fixture.setUp() 339 | self.assertEqual([], results) 340 | fixture.reset() 341 | self.assertEqual([("reset", 21)], results) 342 | self.assertEqual(7, fixture.fn_result) 343 | fixture.cleanUp() 344 | self.assertEqual([("reset", 21), 7], results) 345 | 346 | 347 | class TestMethodFixture(testtools.TestCase): 348 | def test_no_setup_cleanup(self): 349 | class Stub: 350 | pass 351 | 352 | fixture = fixtures.MethodFixture(Stub()) 353 | fixture.setUp() 354 | fixture.reset() 355 | self.assertIsInstance(fixture.obj, Stub) 356 | fixture.cleanUp() 357 | 358 | def test_setup_only(self): 359 | class Stub: 360 | def setUp(self): 361 | self.value = 42 362 | 363 | fixture = fixtures.MethodFixture(Stub()) 364 | fixture.setUp() 365 | self.assertEqual(42, fixture.obj.value) 366 | self.assertIsInstance(fixture.obj, Stub) 367 | fixture.cleanUp() 368 | 369 | def test_cleanup_only(self): 370 | class Stub: 371 | value = None 372 | 373 | def tearDown(self): 374 | self.value = 42 375 | 376 | fixture = fixtures.MethodFixture(Stub()) 377 | fixture.setUp() 378 | self.assertEqual(None, fixture.obj.value) 379 | self.assertIsInstance(fixture.obj, Stub) 380 | fixture.cleanUp() 381 | self.assertEqual(42, fixture.obj.value) 382 | 383 | def test_cleanup(self): 384 | class Stub: 385 | def setUp(self): 386 | self.value = 42 387 | 388 | def tearDown(self): 389 | self.value = 84 390 | 391 | fixture = fixtures.MethodFixture(Stub()) 392 | fixture.setUp() 393 | self.assertEqual(42, fixture.obj.value) 394 | self.assertIsInstance(fixture.obj, Stub) 395 | fixture.cleanUp() 396 | self.assertEqual(84, fixture.obj.value) 397 | 398 | def test_custom_setUp(self): 399 | class Stub: 400 | def mysetup(self): 401 | self.value = 42 402 | 403 | obj = Stub() 404 | fixture = fixtures.MethodFixture(obj, setup=obj.mysetup) 405 | fixture.setUp() 406 | self.assertEqual(42, fixture.obj.value) 407 | self.assertEqual(obj, fixture.obj) 408 | fixture.cleanUp() 409 | 410 | def test_custom_cleanUp(self): 411 | class Stub: 412 | value = 42 413 | 414 | def mycleanup(self): 415 | self.value = None 416 | 417 | obj = Stub() 418 | fixture = fixtures.MethodFixture(obj, cleanup=obj.mycleanup) 419 | fixture.setUp() 420 | self.assertEqual(42, fixture.obj.value) 421 | self.assertEqual(obj, fixture.obj) 422 | fixture.cleanUp() 423 | self.assertEqual(None, fixture.obj.value) 424 | 425 | def test_reset(self): 426 | class Stub: 427 | def setUp(self): 428 | self.value = 42 429 | 430 | def tearDown(self): 431 | self.value = 84 432 | 433 | def reset(self): 434 | self.value = 126 435 | 436 | obj = Stub() 437 | fixture = fixtures.MethodFixture(obj, reset=obj.reset) 438 | fixture.setUp() 439 | self.assertEqual(obj, fixture.obj) 440 | self.assertEqual(42, obj.value) 441 | fixture.reset() 442 | self.assertEqual(126, obj.value) 443 | fixture.cleanUp() 444 | self.assertEqual(84, obj.value) 445 | -------------------------------------------------------------------------------- /fixtures/tests/test_testcase.py: -------------------------------------------------------------------------------- 1 | # fixtures: Fixtures with cleanups for testing and convenience. 2 | # 3 | # Copyright (c) 2010, Robert Collins 4 | # 5 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 6 | # license at the users choice. A copy of both licenses are available in the 7 | # project source as Apache-2.0 and BSD. You may not use this file except in 8 | # compliance with one of these two licences. 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # license you chose for the specific language governing permissions and 14 | # limitations under that license. 15 | 16 | import unittest 17 | import testtools 18 | from testtools.content import text_content 19 | from testtools.testcase import skipIf 20 | 21 | import fixtures 22 | from fixtures import TestWithFixtures 23 | from fixtures.fixture import gather_details 24 | from fixtures.tests.helpers import LoggingFixture 25 | 26 | 27 | class TestTestWithFixtures(unittest.TestCase): 28 | def test_useFixture(self): 29 | fixture = LoggingFixture() 30 | 31 | class SimpleTest(testtools.TestCase, TestWithFixtures): 32 | def test_foo(self): 33 | self.useFixture(fixture) 34 | 35 | result = unittest.TestResult() 36 | SimpleTest("test_foo").run(result) 37 | self.assertTrue(result.wasSuccessful()) 38 | self.assertEqual(["setUp", "cleanUp"], fixture.calls) 39 | 40 | def test_useFixture_uses_raise_first(self): 41 | calls = [] 42 | 43 | def raiser(ignored): 44 | calls.append("called") 45 | raise Exception("foo") 46 | 47 | fixture = fixtures.FunctionFixture(lambda: None, raiser) 48 | 49 | class SimpleTest(testtools.TestCase, TestWithFixtures): 50 | def test_foo(self): 51 | self.useFixture(fixture) 52 | 53 | result = unittest.TestResult() 54 | SimpleTest("test_foo").run(result) 55 | self.assertFalse(result.wasSuccessful()) 56 | self.assertEqual(["called"], calls) 57 | 58 | @skipIf(gather_details is None, "gather_details() is not available.") 59 | def test_useFixture_details_captured_from_setUp(self): 60 | # Details added during fixture set-up are gathered even if setUp() 61 | # fails with an exception. 62 | class SomethingBroke(Exception): 63 | pass 64 | 65 | class BrokenFixture(fixtures.Fixture): 66 | def setUp(self): 67 | super(BrokenFixture, self).setUp() 68 | self.addDetail("content", text_content("foobar")) 69 | raise SomethingBroke() 70 | 71 | broken_fixture = BrokenFixture() 72 | 73 | class DetailedTestCase(TestWithFixtures, testtools.TestCase): 74 | def setUp(self): 75 | super(DetailedTestCase, self).setUp() 76 | self.useFixture(broken_fixture) 77 | 78 | def test(self): 79 | pass 80 | 81 | detailed_test_case = DetailedTestCase("test") 82 | self.assertRaises(SomethingBroke, detailed_test_case.setUp) 83 | self.assertEqual( 84 | {"content": text_content("foobar")}, broken_fixture.getDetails() 85 | ) 86 | self.assertEqual( 87 | {"content": text_content("foobar")}, 88 | detailed_test_case.getDetails(), 89 | ) 90 | 91 | @skipIf(gather_details is None, "gather_details() is not available.") 92 | def test_useFixture_details_not_captured_from_setUp(self): 93 | # Details added during fixture set-up are not gathered if the test 94 | # case does not have the ability to accept those details. 95 | class SomethingBroke(Exception): 96 | pass 97 | 98 | class BrokenFixture(fixtures.Fixture): 99 | def setUp(self): 100 | super(BrokenFixture, self).setUp() 101 | self.addDetail("content", text_content("foobar")) 102 | raise SomethingBroke() 103 | 104 | broken_fixture = BrokenFixture() 105 | 106 | class NonDetailedTestCase(TestWithFixtures, unittest.TestCase): 107 | def setUp(self): 108 | super(NonDetailedTestCase, self).setUp() 109 | self.useFixture(broken_fixture) 110 | 111 | def test(self): 112 | pass 113 | 114 | non_detailed_test_case = NonDetailedTestCase("test") 115 | self.assertRaises(SomethingBroke, non_detailed_test_case.setUp) 116 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling", "hatch-vcs"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "fixtures" 7 | description = "Fixtures, reusable state for writing clean tests and more." 8 | readme = "README.rst" 9 | classifiers = [ 10 | "Development Status :: 6 - Mature", 11 | "Intended Audience :: Developers", 12 | "License :: OSI Approved :: BSD License", 13 | "License :: OSI Approved :: Apache Software License", 14 | "Operating System :: OS Independent", 15 | "Programming Language :: Python", 16 | "Programming Language :: Python :: 3", 17 | "Programming Language :: Python :: 3.8", 18 | "Programming Language :: Python :: 3.9", 19 | "Programming Language :: Python :: 3.10", 20 | "Programming Language :: Python :: 3.11", 21 | "Programming Language :: Python :: 3.12", 22 | "Programming Language :: Python :: 3.13", 23 | "Programming Language :: Python :: Implementation :: CPython", 24 | "Programming Language :: Python :: Implementation :: PyPy", 25 | "Topic :: Software Development :: Quality Assurance", 26 | "Topic :: Software Development :: Testing", 27 | ] 28 | authors = [{name = "Robert Collins", email = "robertc@robertcollins.net"}] 29 | license = {text = "Apache-2.0 or BSD"} 30 | requires-python = ">=3.8" 31 | dynamic = ["version"] 32 | 33 | [project.urls] 34 | Homepage = "https://github.com/testing-cabal/fixtures" 35 | "Bug Tracker" = "https://github.com/testing-cabal/fixtures/issues" 36 | "Source Code" = "https://github.com/testing-cabal/fixtures" 37 | 38 | [project.optional-dependencies] 39 | "streams" = ["testools"] 40 | "test" = ["testtools"] 41 | "docs" = ["docutils"] 42 | 43 | [tool.hatch.version] 44 | source = "vcs" 45 | 46 | [tool.hatch.build.hooks.vcs] 47 | version-file = "fixtures/_version.py" 48 | 49 | [tool.hatch.build.targets.sdist] 50 | include = [ 51 | "fixtures*", 52 | "Apache-2.0", 53 | "BSD", 54 | "ChangeLog", 55 | "COPYING", 56 | "GOALS", 57 | "HACKING", 58 | "Makefile", 59 | "NEWS", 60 | "tox.ini", 61 | ] 62 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py38,py39,py310,py311,py312,py313,pypy3 3 | minversion = 3.1 4 | 5 | [testenv] 6 | usedevelop = true 7 | extras = 8 | docs 9 | test 10 | commands = python -m testtools.run fixtures.test_suite 11 | --------------------------------------------------------------------------------