From: Jelmer Vernooij Date: Sun, 14 Dec 2014 20:29:12 +0000 (+0000) Subject: Remove bundled testtools. X-Git-Url: http://git.samba.org/?a=commitdiff_plain;h=8918481a8415c76b83230067162a53935a4cce4a;p=obnox%2Fsamba%2Fsamba-obnox.git Remove bundled testtools. Change-Id: Ic6ddb352e403c9591cbe4ad3fd36758ffcc2ddb9 Signed-off-by: Jelmer Vernooij Reviewed-by: Andrew Bartlett --- diff --git a/lib/testtools/.testr.conf b/lib/testtools/.testr.conf deleted file mode 100644 index 8a65628adbb..00000000000 --- a/lib/testtools/.testr.conf +++ /dev/null @@ -1,4 +0,0 @@ -[DEFAULT] -test_command=${PYTHON:-python} -m subunit.run discover . $LISTOPT $IDOPTION -test_id_option=--load-list $IDFILE -test_list_option=--list diff --git a/lib/testtools/LICENSE b/lib/testtools/LICENSE deleted file mode 100644 index d59dc7cd282..00000000000 --- a/lib/testtools/LICENSE +++ /dev/null @@ -1,58 +0,0 @@ -Copyright (c) 2008-2011 Jonathan M. Lange and the testtools -authors. - -The testtools authors are: - * Canonical Ltd - * Twisted Matrix Labs - * Jonathan Lange - * Robert Collins - * Andrew Bennetts - * Benjamin Peterson - * Jamu Kakar - * James Westby - * Martin [gz] - * Michael Hudson-Doyle - * Aaron Bentley - * Christian Kampka - * Gavin Panella - * Martin Pool - * Vincent Ladeuil - -and are collectively referred to as "testtools developers". - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - -Some code in testtools/run.py taken from Python's unittest module: -Copyright (c) 1999-2003 Steve Purcell -Copyright (c) 2003-2010 Python Software Foundation - -This module is free software, and you may redistribute it and/or modify -it under the same terms as Python itself, so long as this copyright message -and disclaimer are retained in their original form. - -IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, -SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF -THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. - -THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, -AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, -SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. diff --git a/lib/testtools/MANIFEST.in b/lib/testtools/MANIFEST.in deleted file mode 100644 index 7da191a41e0..00000000000 --- a/lib/testtools/MANIFEST.in +++ /dev/null @@ -1,10 +0,0 @@ -include LICENSE -include Makefile -include MANIFEST.in -include NEWS -include README -include .bzrignore -graft doc -graft doc/_static -graft doc/_templates -prune doc/_build diff --git a/lib/testtools/Makefile b/lib/testtools/Makefile deleted file mode 100644 index b3e40ecddfb..00000000000 --- a/lib/testtools/Makefile +++ /dev/null @@ -1,56 +0,0 @@ -# See README for copyright and licensing details. - -PYTHON=python -SOURCES=$(shell find testtools -name "*.py") - -check: - PYTHONPATH=$(PWD) $(PYTHON) -m testtools.run testtools.tests.test_suite - -TAGS: ${SOURCES} - ctags -e -R testtools/ - -tags: ${SOURCES} - ctags -R testtools/ - -clean: clean-sphinx - rm -f TAGS tags - find testtools -name "*.pyc" -exec rm '{}' \; - -prerelease: - # An existing MANIFEST breaks distutils sometimes. Avoid that. - -rm MANIFEST - -release: - ./setup.py sdist upload --sign - $(PYTHON) scripts/_lp_release.py - -snapshot: prerelease - ./setup.py sdist - -### Documentation ### - -apidocs: - # pydoctor emits deprecation warnings under Ubuntu 10.10 LTS - PYTHONWARNINGS='ignore::DeprecationWarning' \ - pydoctor --make-html --add-package testtools \ - --docformat=restructuredtext --project-name=testtools \ - --project-url=https://launchpad.net/testtools - -doc/news.rst: - ln -s ../NEWS doc/news.rst - -docs: doc/news.rst docs-sphinx - rm doc/news.rst - -docs-sphinx: html-sphinx - -# Clean out generated documentation -clean-sphinx: - cd doc && make clean - -# Build the html docs using Sphinx. -html-sphinx: - cd doc && make html - -.PHONY: apidocs docs-sphinx clean-sphinx html-sphinx docs -.PHONY: check clean prerelease release diff --git a/lib/testtools/NEWS b/lib/testtools/NEWS deleted file mode 100644 index ac88fc13649..00000000000 --- a/lib/testtools/NEWS +++ /dev/null @@ -1,1413 +0,0 @@ -testtools NEWS -++++++++++++++ - -Changes and improvements to testtools_, grouped by release. - -NEXT -~~~~ - -Changes -------- - -* Fixed unit tests which were failing under pypy due to a change in the way - pypy formats tracebacks. (Thomi Richards) - -* Make `testtools.content.text_content` error if anything other than text - is given as content. (Thomi Richards) - -* We now publish wheels of testtools. (Robert Collins, #issue84) - -1.1.0 -~~~~~ - -Improvements ------------- - -* Exceptions in a ``fixture.getDetails`` method will no longer mask errors - raised from the same fixture's ``setUp`` method. - (Robert Collins, #1368440) - -1.0.0 -~~~~~ - -Long overdue, we've adopted a backwards compatibility statement and recognized -that we have plenty of users depending on our behaviour - calling our version -1.0.0 is a recognition of that. - -Improvements ------------- - -* Fix a long-standing bug where tearDown and cleanUps would not be called if the - test run was interrupted. This should fix leaking external resources from - interrupted tests. - (Robert Collins, #1364188) - -* Fix a long-standing bug where calling sys.exit(0) from within a test would - cause the test suite to exit with 0, without reporting a failure of that - test. We still allow the test suite to be exited (since catching higher order - exceptions requires exceptional circumstances) but we now call a last-resort - handler on the TestCase, resulting in an error being reported for the test. - (Robert Collins, #1364188) - -* Fix an issue where tests skipped with the ``skip``* family of decorators would - still have their ``setUp`` and ``tearDown`` functions called. - (Thomi Richards, #https://github.com/testing-cabal/testtools/issues/86) - -* We have adopted a formal backwards compatibility statement (see hacking.rst) - (Robert Collins) - -0.9.39 -~~~~~~ - -Brown paper bag release - 0.9.38 was broken for some users, -_jython_aware_splitext was not defined entirely compatibly. -(Robert Collins, #https://github.com/testing-cabal/testtools/issues/100) - -0.9.38 -~~~~~~ - -Bug fixes for test importing. - -Improvements ------------- - -* Discovery import error detection wasn't implemented for python 2.6 (the - 'discover' module). (Robert Collins) - -* Discovery now executes load_tests (if present) in __init__ in all packages. - (Robert Collins, http://bugs.python.org/issue16662) - -0.9.37 -~~~~~~ - -Minor improvements to correctness. - -Changes -------- - -* ``stdout`` is now correctly honoured on ``run.TestProgram`` - before the - runner objects would be created with no stdout parameter. If construction - fails, the previous parameter list is attempted, permitting compatibility - with Runner classes that don't accept stdout as a parameter. - (Robert Collins) - -* The ``ExtendedToStreamDecorator`` now handles content objects with one less - packet - the last packet of the source content is sent with EOF set rather - than an empty packet with EOF set being sent after the last packet of the - source content. (Robert Collins) - -0.9.36 -~~~~~~ - -Welcome to our long overdue 0.9.36 release, which improves compatibility with -Python3.4, adds assert_that, a function for using matchers without TestCase -objects, and finally will error if you try to use setUp or tearDown twice - -since that invariably leads to bad things of one sort or another happening. - -Changes -------- - -* Error if ``setUp`` or ``tearDown`` are called twice. - (Robert Collins, #882884) - -* Make testtools compatible with the ``unittest.expectedFailure`` decorator in - Python 3.4. (Thomi Richards) - - -Improvements ------------- - -* Introduce the assert_that function, which allows matchers to be used - independent of testtools.TestCase. (Daniel Watkins, #1243834) - - -0.9.35 -~~~~~~ - -Changes -------- - -* Removed a number of code paths where Python 2.4 and Python 2.5 were - explicitly handled. (Daniel Watkins) - -Improvements ------------- - -* Added the ``testtools.TestCase.expectThat`` method, which implements - delayed assertions. (Thomi Richards) - -* Docs are now built as part of the Travis-CI build, reducing the chance of - Read The Docs being broken accidentally. (Daniel Watkins, #1158773) - -0.9.34 -~~~~~~ - -Improvements ------------- - -* Added ability for ``testtools.TestCase`` instances to force a test to - fail, even if no assertions failed. (Thomi Richards) - -* Added ``testtools.content.StacktraceContent``, a content object that - automatically creates a ``StackLinesContent`` object containing the current - stack trace. (Thomi Richards) - -* ``AnyMatch`` is now exported properly in ``testtools.matchers``. - (Robert Collins, Rob Kennedy, github #44) - -* In Python 3.3, if there are duplicate test ids, tests.sort() will - fail and raise TypeError. Detect the duplicate test ids firstly in - sorted_tests() to ensure that all test ids are unique. - (Kui Shi, #1243922) - -* ``json_content`` is now in the ``__all__`` attribute for - ``testtools.content``. (Robert Collins) - -* Network tests now bind to 127.0.0.1 to avoid (even temporary) network - visible ports. (Benedikt Morbach, github #46) - -* Test listing now explicitly indicates by printing 'Failed to import' and - exiting (2) when an import has failed rather than only signalling through the - test name. (Robert Collins, #1245672) - -* ``test_compat.TestDetectEncoding.test_bom`` now works on Python 3.3 - the - corner case with euc_jp is no longer permitted in Python 3.3 so we can - skip it. (Martin [gz], #1251962) - -0.9.33 -~~~~~~ - -Improvements ------------- - -* Added ``addDetailuniqueName`` method to ``testtools.TestCase`` class. - (Thomi Richards) - -* Removed some unused code from ``testtools.content.TracebackContent``. - (Thomi Richards) - -* Added ``testtools.StackLinesContent``: a content object for displaying - pre-processed stack lines. (Thomi Richards) - -* ``StreamSummary`` was calculating testsRun incorrectly: ``exists`` status - tests were counted as run tests, but they are not. - (Robert Collins, #1203728) - -0.9.32 -~~~~~~ - -Regular maintenance release. Special thanks to new contributor, Xiao Hanyu! - -Changes -------- - - * ``testttols.compat._format_exc_info`` has been refactored into several - smaller functions. (Thomi Richards) - -Improvements ------------- - -* Stacktrace filtering no longer hides unittest frames that are surrounded by - user frames. We will reenable this when we figure out a better algorithm for - retaining meaning. (Robert Collins, #1188420) - -* The compatibility code for skipped tests with unittest2 was broken. - (Robert Collins, #1190951) - -* Various documentation improvements (Clint Byrum, Xiao Hanyu). - -0.9.31 -~~~~~~ - -Improvements ------------- - -* ``ExpectedException`` now accepts a msg parameter for describing an error, - much the same as assertEquals etc. (Robert Collins) - -0.9.30 -~~~~~~ - -A new sort of TestResult, the StreamResult has been added, as a prototype for -a revised standard library test result API. Expect this API to change. -Although we will try to preserve compatibility for early adopters, it is -experimental and we might need to break it if it turns out to be unsuitable. - -Improvements ------------- -* ``assertRaises`` works properly for exception classes that have custom - metaclasses - -* ``ConcurrentTestSuite`` was silently eating exceptions that propagate from - the test.run(result) method call. Ignoring them is fine in a normal test - runner, but when they happen in a different thread, the thread that called - suite.run() is not in the stack anymore, and the exceptions are lost. We now - create a synthetic test recording any such exception. - (Robert Collins, #1130429) - -* Fixed SyntaxError raised in ``_compat2x.py`` when installing via Python 3. - (Will Bond, #941958) - -* New class ``StreamResult`` which defines the API for the new result type. - (Robert Collins) - -* New support class ``ConcurrentStreamTestSuite`` for convenient construction - and utilisation of ``StreamToQueue`` objects. (Robert Collins) - -* New support class ``CopyStreamResult`` which forwards events onto multiple - ``StreamResult`` objects (each of which receives all the events). - (Robert Collins) - -* New support class ``StreamSummary`` which summarises a ``StreamResult`` - stream compatibly with ``TestResult`` code. (Robert Collins) - -* New support class ``StreamTagger`` which adds or removes tags from - ``StreamResult`` events. (RobertCollins) - -* New support class ``StreamToDict`` which converts a ``StreamResult`` to a - series of dicts describing a test. Useful for writing trivial stream - analysers. (Robert Collins) - -* New support class ``TestControl`` which permits cancelling an in-progress - run. (Robert Collins) - -* New support class ``StreamFailFast`` which calls a ``TestControl`` instance - to abort the test run when a failure is detected. (Robert Collins) - -* New support class ``ExtendedToStreamDecorator`` which translates both regular - unittest TestResult API calls and the ExtendedTestResult API which testtools - has supported into the StreamResult API. ExtendedToStreamDecorator also - forwards calls made in the StreamResult API, permitting it to be used - anywhere a StreamResult is used. Key TestResult query methods like - wasSuccessful and shouldStop are synchronised with the StreamResult API - calls, but the detailed statistics like the list of errors are not - a - separate consumer will be created to support that. - (Robert Collins) - -* New support class ``StreamToExtendedDecorator`` which translates - ``StreamResult`` API calls into ``ExtendedTestResult`` (or any older - ``TestResult``) calls. This permits using un-migrated result objects with - new runners / tests. (Robert Collins) - -* New support class ``StreamToQueue`` for sending messages to one - ``StreamResult`` from multiple threads. (Robert Collins) - -* New support class ``TimestampingStreamResult`` which adds a timestamp to - events with no timestamp. (Robert Collins) - -* New ``TestCase`` decorator ``DecorateTestCaseResult`` that adapts the - ``TestResult`` or ``StreamResult`` a case will be run with, for ensuring that - a particular result object is used even if the runner running the test doesn't - know to use it. (Robert Collins) - -* New test support class ``testtools.testresult.doubles.StreamResult``, which - captures all the StreamResult events. (Robert Collins) - -* ``PlaceHolder`` can now hold tags, and applies them before, and removes them - after, the test. (Robert Collins) - -* ``PlaceHolder`` can now hold timestamps, and applies them before the test and - then before the outcome. (Robert Collins) - -* ``StreamResultRouter`` added. This is useful for demultiplexing - e.g. for - partitioning analysis of events or sending feedback encapsulated in - StreamResult events back to their source. (Robert Collins) - -* ``testtools.run.TestProgram`` now supports the ``TestRunner`` taking over - responsibility for formatting the output of ``--list-tests``. - (Robert Collins) - -* The error message for setUp and tearDown upcall errors was broken on Python - 3.4. (Monty Taylor, Robert Collins, #1140688) - -* The repr of object() on pypy includes the object id, which was breaking a - test that accidentally depended on the CPython repr for object(). - (Jonathan Lange) - -0.9.29 -~~~~~~ - -A simple bug fix, and better error messages when you don't up-call. - -Changes -------- - -* ``testtools.content_type.ContentType`` incorrectly used ',' rather than ';' - to separate parameters. (Robert Collins) - -Improvements ------------- - -* ``testtools.compat.unicode_output_stream`` was wrapping a stream encoder - around ``io.StringIO`` and ``io.TextIOWrapper`` objects, which was incorrect. - (Robert Collins) - -* Report the name of the source file for setUp and tearDown upcall errors. - (Monty Taylor) - -0.9.28 -~~~~~~ - -Testtools has moved VCS - https://github.com/testing-cabal/testtools/ is -the new home. Bug tracking is still on Launchpad, and releases are on Pypi. - -We made this change to take advantage of the richer ecosystem of tools around -Git, and to lower the barrier for new contributors. - -Improvements ------------- - -* New ``testtools.testcase.attr`` and ``testtools.testcase.WithAttributes`` - helpers allow marking up test case methods with simple labels. This permits - filtering tests with more granularity than organising them into modules and - test classes. (Robert Collins) - -0.9.27 -~~~~~~ - -Improvements ------------- - -* New matcher ``HasLength`` for matching the length of a collection. - (Robert Collins) - -* New matcher ``MatchesPredicateWithParams`` make it still easier to create - ad hoc matchers. (Robert Collins) - -* We have a simpler release process in future - see doc/hacking.rst. - (Robert Collins) - -0.9.26 -~~~~~~ - -Brown paper bag fix: failed to document the need for setup to be able to use -extras. Compounded by pip not supporting setup_requires. - -Changes -------- - -* setup.py now can generate egg_info even if extras is not available. - Also lists extras in setup_requires for easy_install. - (Robert Collins, #1102464) - -0.9.25 -~~~~~~ - -Changes -------- - -* ``python -m testtools.run --load-list`` will now preserve any custom suites - (such as ``testtools.FixtureSuite`` or ``testresources.OptimisingTestSuite``) - rather than flattening them. - (Robert Collins, #827175) - -* Testtools now depends on extras, a small library split out from it to contain - generally useful non-testing facilities. Since extras has been around for a - couple of testtools releases now, we're making this into a hard dependency of - testtools. (Robert Collins) - -* Testtools now uses setuptools rather than distutils so that we can document - the extras dependency. (Robert Collins) - -Improvements ------------- - -* Testtools will no longer override test code registered details called - 'traceback' when reporting caught exceptions from test code. - (Robert Collins, #812793) - -0.9.24 -~~~~~~ - -Changes -------- - -* ``testtools.run discover`` will now sort the tests it discovered. This is a - workaround for http://bugs.python.org/issue16709. Non-standard test suites - are preserved, and their ``sort_tests()`` method called (if they have such an - attribute). ``testtools.testsuite.sorted_tests(suite, True)`` can be used by - such suites to do a local sort. (Robert Collins, #1091512) - -* ``ThreadsafeForwardingResult`` now defines a stub ``progress`` method, which - fixes ``testr run`` of streams containing progress markers (by discarding the - progress data). (Robert Collins, #1019165) - -0.9.23 -~~~~~~ - -Changes -------- - -* ``run.TestToolsTestRunner`` now accepts the verbosity, buffer and failfast - arguments the upstream python TestProgram code wants to give it, making it - possible to support them in a compatible fashion. (Robert Collins) - -Improvements ------------- - -* ``testtools.run`` now supports the ``-f`` or ``--failfast`` parameter. - Previously it was advertised in the help but ignored. - (Robert Collins, #1090582) - -* ``AnyMatch`` added, a new matcher that matches when any item in a collection - matches the given matcher. (Jonathan Lange) - -* Spelling corrections to documentation. (Vincent Ladeuil) - -* ``TestProgram`` now has a sane default for its ``testRunner`` argument. - (Vincent Ladeuil) - -* The test suite passes on Python 3 again. (Robert Collins) - -0.9.22 -~~~~~~ - -Improvements ------------- - -* ``content_from_file`` and ``content_from_stream`` now accept seek_offset and - seek_whence parameters allowing them to be used to grab less than the full - stream, or to be used with StringIO streams. (Robert Collins, #1088693) - -0.9.21 -~~~~~~ - -Improvements ------------- - -* ``DirContains`` correctly exposed, after being accidentally hidden in the - great matcher re-organization of 0.9.17. (Jonathan Lange) - - -0.9.20 -~~~~~~ - -Three new matchers that'll rock your world. - -Improvements ------------- - -* New, powerful matchers that match items in a dictionary: - - - ``MatchesDict``, match every key in a dictionary with a key in a - dictionary of matchers. For when the set of expected keys is equal to the - set of observed keys. - - - ``ContainsDict``, every key in a dictionary of matchers must be - found in a dictionary, and the values for those keys must match. For when - the set of expected keys is a subset of the set of observed keys. - - - ``ContainedByDict``, every key in a dictionary must be found in - a dictionary of matchers. For when the set of expected keys is a superset - of the set of observed keys. - - The names are a little confusing, sorry. We're still trying to figure out - how to present the concept in the simplest way possible. - - -0.9.19 -~~~~~~ - -How embarrassing! Three releases in two days. - -We've worked out the kinks and have confirmation from our downstreams that -this is all good. Should be the last release for a little while. Please -ignore 0.9.18 and 0.9.17. - -Improvements ------------- - -* Include the matcher tests in the release, allowing the tests to run and - pass from the release tarball. (Jonathan Lange) - -* Fix cosmetic test failures in Python 3.3, introduced during release 0.9.17. - (Jonathan Lange) - - -0.9.18 -~~~~~~ - -Due to an oversight, release 0.9.18 did not contain the new -``testtools.matchers`` package and was thus completely broken. This release -corrects that, returning us all to normality. - -0.9.17 -~~~~~~ - -This release brings better discover support and Python3.x improvements. There -are still some test failures on Python3.3 but they are cosmetic - the library -is as usable there as on any other Python 3 release. - -Changes -------- - -* The ``testtools.matchers`` package has been split up. No change to the - public interface. (Jonathan Lange) - -Improvements ------------- - -* ``python -m testtools.run discover . --list`` now works. (Robert Collins) - -* Correctly handling of bytes vs text in JSON content type. (Martin [gz]) - - -0.9.16 -~~~~~~ - -Some new matchers and a new content helper for JSON content. - -This is the first release of testtools to drop support for Python 2.4 and 2.5. -If you need support for either of those versions, please use testtools 0.9.15. - -Improvements ------------- - -* New content helper, ``json_content`` (Jonathan Lange) - -* New matchers: - - * ``ContainsAll`` for asserting one thing is a subset of another - (Raphaël Badin) - - * ``SameMembers`` for asserting two iterators have the same members. - (Jonathan Lange) - -* Reraising of exceptions in Python 3 is more reliable. (Martin [gz]) - - -0.9.15 -~~~~~~ - -This is the last release to support Python2.4 and 2.5. It brings in a slew of -improvements to test tagging and concurrency, making running large test suites -with partitioned workers more reliable and easier to reproduce exact test -ordering in a given worker. See our sister project ``testrepository`` for a -test runner that uses these features. - -Changes -------- - -* ``PlaceHolder`` and ``ErrorHolder`` now support being given result details. - (Robert Collins) - -* ``ErrorHolder`` is now just a function - all the logic is in ``PlaceHolder``. - (Robert Collins) - -* ``TestResult`` and all other ``TestResult``-like objects in testtools - distinguish between global tags and test-local tags, as per the subunit - specification. (Jonathan Lange) - -* This is the **last** release of testtools that supports Python 2.4 or 2.5. - These releases are no longer supported by the Python community and do not - receive security updates. If this affects you, you will need to either - stay on this release or perform your own backports. - (Jonathan Lange, Robert Collins) - -* ``ThreadsafeForwardingResult`` now forwards global tags as test-local tags, - making reasoning about the correctness of the multiplexed stream simpler. - This preserves the semantic value (what tags apply to a given test) while - consuming less stream size (as no negative-tag statement is needed). - (Robert Collins, Gary Poster, #986434) - -Improvements ------------- - -* API documentation corrections. (Raphaël Badin) - -* ``ConcurrentTestSuite`` now takes an optional ``wrap_result`` parameter - that can be used to wrap the ``ThreadsafeForwardingResult``s created by - the suite. (Jonathan Lange) - -* ``Tagger`` added. It's a new ``TestResult`` that tags all tests sent to - it with a particular set of tags. (Jonathan Lange) - -* ``testresultdecorator`` brought over from subunit. (Jonathan Lange) - -* All ``TestResult`` wrappers now correctly forward ``current_tags`` from - their wrapped results, meaning that ``current_tags`` can always be relied - upon to return the currently active tags on a test result. - -* ``TestByTestResult``, a ``TestResult`` that calls a method once per test, - added. (Jonathan Lange) - -* ``ThreadsafeForwardingResult`` correctly forwards ``tags()`` calls where - only one of ``new_tags`` or ``gone_tags`` are specified. - (Jonathan Lange, #980263) - -* ``ThreadsafeForwardingResult`` no longer leaks local tags from one test - into all future tests run. (Jonathan Lange, #985613) - -* ``ThreadsafeForwardingResult`` has many, many more tests. (Jonathan Lange) - - -0.9.14 -~~~~~~ - -Our sister project, `subunit `_, was using a -private API that was deleted in the 0.9.13 release. This release restores -that API in order to smooth out the upgrade path. - -If you don't use subunit, then this release won't matter very much to you. - - -0.9.13 -~~~~~~ - -Plenty of new matchers and quite a few critical bug fixes (especially to do -with stack traces from failed assertions). A net win for all. - -Changes -------- - -* ``MatchesAll`` now takes an ``first_only`` keyword argument that changes how - mismatches are displayed. If you were previously passing matchers to - ``MatchesAll`` with keyword arguments, then this change might affect your - test results. (Jonathan Lange) - -Improvements ------------- - -* Actually hide all of the testtools stack for assertion failures. The - previous release promised clean stack, but now we actually provide it. - (Jonathan Lange, #854769) - -* ``assertRaises`` now includes the ``repr`` of the callable that failed to raise - properly. (Jonathan Lange, #881052) - -* Asynchronous tests no longer hang when run with trial. - (Jonathan Lange, #926189) - -* ``Content`` objects now have an ``as_text`` method to convert their contents - to Unicode text. (Jonathan Lange) - -* Failed equality assertions now line up. (Jonathan Lange, #879339) - -* ``FullStackRunTest`` no longer aborts the test run if a test raises an - error. (Jonathan Lange) - -* ``MatchesAll`` and ``MatchesListwise`` both take a ``first_only`` keyword - argument. If True, they will report only on the first mismatch they find, - and not continue looking for other possible mismatches. - (Jonathan Lange) - -* New helper, ``Nullary`` that turns callables with arguments into ones that - don't take arguments. (Jonathan Lange) - -* New matchers: - - * ``DirContains`` matches the contents of a directory. - (Jonathan Lange, James Westby) - - * ``DirExists`` matches if a directory exists. - (Jonathan Lange, James Westby) - - * ``FileContains`` matches the contents of a file. - (Jonathan Lange, James Westby) - - * ``FileExists`` matches if a file exists. - (Jonathan Lange, James Westby) - - * ``HasPermissions`` matches the permissions of a file. (Jonathan Lange) - - * ``MatchesPredicate`` matches if a predicate is true. (Jonathan Lange) - - * ``PathExists`` matches if a path exists. (Jonathan Lange, James Westby) - - * ``SamePath`` matches if two paths are the same. (Jonathan Lange) - - * ``TarballContains`` matches the contents of a tarball. (Jonathan Lange) - -* ``MultiTestResult`` supports the ``tags`` method. - (Graham Binns, Francesco Banconi, #914279) - -* ``ThreadsafeForwardingResult`` supports the ``tags`` method. - (Graham Binns, Francesco Banconi, #914279) - -* ``ThreadsafeForwardingResult`` no longer includes semaphore acquisition time - in the test duration (for implicitly timed test runs). - (Robert Collins, #914362) - -0.9.12 -~~~~~~ - -This is a very big release. We've made huge improvements on three fronts: - 1. Test failures are way nicer and easier to read - 2. Matchers and ``assertThat`` are much more convenient to use - 3. Correct handling of extended unicode characters - -We've trimmed off the fat from the stack trace you get when tests fail, we've -cut out the bits of error messages that just didn't help, we've made it easier -to annotate mismatch failures, to compare complex objects and to match raised -exceptions. - -Testing code was never this fun. - -Changes -------- - -* ``AfterPreproccessing`` renamed to ``AfterPreprocessing``, which is a more - correct spelling. Old name preserved for backwards compatibility, but is - now deprecated. Please stop using it. - (Jonathan Lange, #813460) - -* ``assertThat`` raises ``MismatchError`` instead of - ``TestCase.failureException``. ``MismatchError`` is a subclass of - ``AssertionError``, so in most cases this change will not matter. However, - if ``self.failureException`` has been set to a non-default value, then - mismatches will become test errors rather than test failures. - -* ``gather_details`` takes two dicts, rather than two detailed objects. - (Jonathan Lange, #801027) - -* ``MatchesRegex`` mismatch now says " does not match //" rather - than " did not match ". The regular expression contains fewer - backslashes too. (Jonathan Lange, #818079) - -* Tests that run with ``AsynchronousDeferredRunTest`` now have the ``reactor`` - attribute set to the running reactor. (Jonathan Lange, #720749) - -Improvements ------------- - -* All public matchers are now in ``testtools.matchers.__all__``. - (Jonathan Lange, #784859) - -* ``assertThat`` can actually display mismatches and matchers that contain - extended unicode characters. (Jonathan Lange, Martin [gz], #804127) - -* ``assertThat`` output is much less verbose, displaying only what the mismatch - tells us to display. Old-style verbose output can be had by passing - ``verbose=True`` to assertThat. (Jonathan Lange, #675323, #593190) - -* ``assertThat`` accepts a message which will be used to annotate the matcher. - This can be given as a third parameter or as a keyword parameter. - (Robert Collins) - -* Automated the Launchpad part of the release process. - (Jonathan Lange, #623486) - -* Correctly display non-ASCII unicode output on terminals that claim to have a - unicode encoding. (Martin [gz], #804122) - -* ``DocTestMatches`` correctly handles unicode output from examples, rather - than raising an error. (Martin [gz], #764170) - -* ``ErrorHolder`` and ``PlaceHolder`` added to docs. (Jonathan Lange, #816597) - -* ``ExpectedException`` now matches any exception of the given type by - default, and also allows specifying a ``Matcher`` rather than a mere regular - expression. (Jonathan Lange, #791889) - -* ``FixtureSuite`` added, allows test suites to run with a given fixture. - (Jonathan Lange) - -* Hide testtools's own stack frames when displaying tracebacks, making it - easier for test authors to focus on their errors. - (Jonathan Lange, Martin [gz], #788974) - -* Less boilerplate displayed in test failures and errors. - (Jonathan Lange, #660852) - -* ``MatchesException`` now allows you to match exceptions against any matcher, - rather than just regular expressions. (Jonathan Lange, #791889) - -* ``MatchesException`` now permits a tuple of types rather than a single type - (when using the type matching mode). (Robert Collins) - -* ``MatchesStructure.byEquality`` added to make the common case of matching - many attributes by equality much easier. ``MatchesStructure.byMatcher`` - added in case folk want to match by things other than equality. - (Jonathan Lange) - -* New convenience assertions, ``assertIsNone`` and ``assertIsNotNone``. - (Christian Kampka) - -* New matchers: - - * ``AllMatch`` matches many values against a single matcher. - (Jonathan Lange, #615108) - - * ``Contains``. (Robert Collins) - - * ``GreaterThan``. (Christian Kampka) - -* New helper, ``safe_hasattr`` added. (Jonathan Lange) - -* ``reraise`` added to ``testtools.compat``. (Jonathan Lange) - - -0.9.11 -~~~~~~ - -This release brings consistent use of super for better compatibility with -multiple inheritance, fixed Python3 support, improvements in fixture and mather -outputs and a compat helper for testing libraries that deal with bytestrings. - -Changes -------- - -* ``TestCase`` now uses super to call base ``unittest.TestCase`` constructor, - ``setUp`` and ``tearDown``. (Tim Cole, #771508) - -* If, when calling ``useFixture`` an error occurs during fixture set up, we - still attempt to gather details from the fixture. (Gavin Panella) - - -Improvements ------------- - -* Additional compat helper for ``BytesIO`` for libraries that build on - testtools and are working on Python 3 porting. (Robert Collins) - -* Corrected documentation for ``MatchesStructure`` in the test authors - document. (Jonathan Lange) - -* ``LessThan`` error message now says something that is logically correct. - (Gavin Panella, #762008) - -* Multiple details from a single fixture are now kept separate, rather than - being mooshed together. (Gavin Panella, #788182) - -* Python 3 support now back in action. (Martin [gz], #688729) - -* ``try_import`` and ``try_imports`` have a callback that is called whenever - they fail to import a module. (Martin Pool) - - -0.9.10 -~~~~~~ - -The last release of testtools could not be easy_installed. This is considered -severe enough for a re-release. - -Improvements ------------- - -* Include ``doc/`` in the source distribution, making testtools installable - from PyPI again (Tres Seaver, #757439) - - -0.9.9 -~~~~~ - -Many, many new matchers, vastly expanded documentation, stacks of bug fixes, -better unittest2 integration. If you've ever wanted to try out testtools but -been afraid to do so, this is the release to try. - - -Changes -------- - -* The timestamps generated by ``TestResult`` objects when no timing data has - been received are now datetime-with-timezone, which allows them to be - sensibly serialised and transported. (Robert Collins, #692297) - -Improvements ------------- - -* ``AnnotatedMismatch`` now correctly returns details. - (Jonathan Lange, #724691) - -* distutils integration for the testtools test runner. Can now use it for - 'python setup.py test'. (Christian Kampka, #693773) - -* ``EndsWith`` and ``KeysEqual`` now in testtools.matchers.__all__. - (Jonathan Lange, #692158) - -* ``MatchesException`` extended to support a regular expression check against - the str() of a raised exception. (Jonathan Lange) - -* ``MultiTestResult`` now forwards the ``time`` API. (Robert Collins, #692294) - -* ``MultiTestResult`` now documented in the manual. (Jonathan Lange, #661116) - -* New content helpers ``content_from_file``, ``content_from_stream`` and - ``attach_file`` make it easier to attach file-like objects to a - test. (Jonathan Lange, Robert Collins, #694126) - -* New ``ExpectedException`` context manager to help write tests against things - that are expected to raise exceptions. (Aaron Bentley) - -* New matchers: - - * ``MatchesListwise`` matches an iterable of matchers against an iterable - of values. (Michael Hudson-Doyle) - - * ``MatchesRegex`` matches a string against a regular expression. - (Michael Hudson-Doyle) - - * ``MatchesStructure`` matches attributes of an object against given - matchers. (Michael Hudson-Doyle) - - * ``AfterPreproccessing`` matches values against a matcher after passing them - through a callable. (Michael Hudson-Doyle) - - * ``MatchesSetwise`` matches an iterable of matchers against an iterable of - values, without regard to order. (Michael Hudson-Doyle) - -* ``setup.py`` can now build a snapshot when Bazaar is installed but the tree - is not a Bazaar tree. (Jelmer Vernooij) - -* Support for running tests using distutils (Christian Kampka, #726539) - -* Vastly improved and extended documentation. (Jonathan Lange) - -* Use unittest2 exception classes if available. (Jelmer Vernooij) - - -0.9.8 -~~~~~ - -In this release we bring some very interesting improvements: - -* new matchers for exceptions, sets, lists, dicts and more. - -* experimental (works but the contract isn't supported) twisted reactor - support. - -* The built in runner can now list tests and filter tests (the -l and - --load-list options). - -Changes -------- - -* addUnexpectedSuccess is translated to addFailure for test results that don't - know about addUnexpectedSuccess. Further, it fails the entire result for - all testtools TestResults (i.e. wasSuccessful() returns False after - addUnexpectedSuccess has been called). Note that when using a delegating - result such as ThreadsafeForwardingResult, MultiTestResult or - ExtendedToOriginalDecorator then the behaviour of addUnexpectedSuccess is - determined by the delegated to result(s). - (Jonathan Lange, Robert Collins, #654474, #683332) - -* startTestRun will reset any errors on the result. That is, wasSuccessful() - will always return True immediately after startTestRun() is called. This - only applies to delegated test results (ThreadsafeForwardingResult, - MultiTestResult and ExtendedToOriginalDecorator) if the delegated to result - is a testtools test result - we cannot reliably reset the state of unknown - test result class instances. (Jonathan Lange, Robert Collins, #683332) - -* Responsibility for running test cleanups has been moved to ``RunTest``. - This change does not affect public APIs and can be safely ignored by test - authors. (Jonathan Lange, #662647) - -Improvements ------------- - -* New matchers: - - * ``EndsWith`` which complements the existing ``StartsWith`` matcher. - (Jonathan Lange, #669165) - - * ``MatchesException`` matches an exception class and parameters. (Robert - Collins) - - * ``KeysEqual`` matches a dictionary with particular keys. (Jonathan Lange) - -* ``assertIsInstance`` supports a custom error message to be supplied, which - is necessary when using ``assertDictEqual`` on Python 2.7 with a - ``testtools.TestCase`` base class. (Jelmer Vernooij) - -* Experimental support for running tests that return Deferreds. - (Jonathan Lange, Martin [gz]) - -* Provide a per-test decorator, run_test_with, to specify which RunTest - object to use for a given test. (Jonathan Lange, #657780) - -* Fix the runTest parameter of TestCase to actually work, rather than raising - a TypeError. (Jonathan Lange, #657760) - -* Non-release snapshots of testtools will now work with buildout. - (Jonathan Lange, #613734) - -* Malformed SyntaxErrors no longer blow up the test suite. (Martin [gz]) - -* ``MismatchesAll.describe`` no longer appends a trailing newline. - (Michael Hudson-Doyle, #686790) - -* New helpers for conditionally importing modules, ``try_import`` and - ``try_imports``. (Jonathan Lange) - -* ``Raises`` added to the ``testtools.matchers`` module - matches if the - supplied callable raises, and delegates to an optional matcher for validation - of the exception. (Robert Collins) - -* ``raises`` added to the ``testtools.matchers`` module - matches if the - supplied callable raises and delegates to ``MatchesException`` to validate - the exception. (Jonathan Lange) - -* Tests will now pass on Python 2.6.4 : an ``Exception`` change made only in - 2.6.4 and reverted in Python 2.6.5 was causing test failures on that version. - (Martin [gz], #689858). - -* ``testtools.TestCase.useFixture`` has been added to glue with fixtures nicely. - (Robert Collins) - -* ``testtools.run`` now supports ``-l`` to list tests rather than executing - them. This is useful for integration with external test analysis/processing - tools like subunit and testrepository. (Robert Collins) - -* ``testtools.run`` now supports ``--load-list``, which takes a file containing - test ids, one per line, and intersects those ids with the tests found. This - allows fine grained control of what tests are run even when the tests cannot - be named as objects to import (e.g. due to test parameterisation via - testscenarios). (Robert Collins) - -* Update documentation to say how to use testtools.run() on Python 2.4. - (Jonathan Lange, #501174) - -* ``text_content`` conveniently converts a Python string to a Content object. - (Jonathan Lange, James Westby) - - - -0.9.7 -~~~~~ - -Lots of little cleanups in this release; many small improvements to make your -testing life more pleasant. - -Improvements ------------- - -* Cleanups can raise ``testtools.MultipleExceptions`` if they have multiple - exceptions to report. For instance, a cleanup which is itself responsible for - running several different internal cleanup routines might use this. - -* Code duplication between assertEqual and the matcher Equals has been removed. - -* In normal circumstances, a TestCase will no longer share details with clones - of itself. (Andrew Bennetts, bug #637725) - -* Less exception object cycles are generated (reduces peak memory use between - garbage collection). (Martin [gz]) - -* New matchers 'DoesNotStartWith' and 'StartsWith' contributed by Canonical - from the Launchpad project. Written by James Westby. - -* Timestamps as produced by subunit protocol clients are now forwarded in the - ThreadsafeForwardingResult so correct test durations can be reported. - (Martin [gz], Robert Collins, #625594) - -* With unittest from Python 2.7 skipped tests will now show only the reason - rather than a serialisation of all details. (Martin [gz], #625583) - -* The testtools release process is now a little better documented and a little - smoother. (Jonathan Lange, #623483, #623487) - - -0.9.6 -~~~~~ - -Nothing major in this release, just enough small bits and pieces to make it -useful enough to upgrade to. - -In particular, a serious bug in assertThat() has been fixed, it's easier to -write Matchers, there's a TestCase.patch() method for those inevitable monkey -patches and TestCase.assertEqual gives slightly nicer errors. - -Improvements ------------- - -* 'TestCase.assertEqual' now formats errors a little more nicely, in the - style of bzrlib. - -* Added `PlaceHolder` and `ErrorHolder`, TestCase-like objects that can be - used to add results to a `TestResult`. - -* 'Mismatch' now takes optional description and details parameters, so - custom Matchers aren't compelled to make their own subclass. - -* jml added a built-in UTF8_TEXT ContentType to make it slightly easier to - add details to test results. See bug #520044. - -* Fix a bug in our built-in matchers where assertThat would blow up if any - of them failed. All built-in mismatch objects now provide get_details(). - -* New 'Is' matcher, which lets you assert that a thing is identical to - another thing. - -* New 'LessThan' matcher which lets you assert that a thing is less than - another thing. - -* TestCase now has a 'patch()' method to make it easier to monkey-patching - objects in tests. See the manual for more information. Fixes bug #310770. - -* MultiTestResult methods now pass back return values from the results it - forwards to. - -0.9.5 -~~~~~ - -This release fixes some obscure traceback formatting issues that probably -weren't affecting you but were certainly breaking our own test suite. - -Changes -------- - -* Jamu Kakar has updated classes in testtools.matchers and testtools.runtest - to be new-style classes, fixing bug #611273. - -Improvements ------------- - -* Martin[gz] fixed traceback handling to handle cases where extract_tb returns - a source line of None. Fixes bug #611307. - -* Martin[gz] fixed an unicode issue that was causing the tests to fail, - closing bug #604187. - -* testtools now handles string exceptions (although why would you want to use - them?) and formats their tracebacks correctly. Thanks to Martin[gz] for - fixing bug #592262. - -0.9.4 -~~~~~ - -This release overhauls the traceback formatting layer to deal with Python 2 -line numbers and traceback objects often being local user encoded strings -rather than unicode objects. Test discovery has also been added and Python 3.1 -is also supported. Finally, the Mismatch protocol has been extended to let -Matchers collaborate with tests in supplying detailed data about failures. - -Changes -------- - -* testtools.utils has been renamed to testtools.compat. Importing - testtools.utils will now generate a deprecation warning. - -Improvements ------------- - -* Add machinery for Python 2 to create unicode tracebacks like those used by - Python 3. This means testtools no longer throws on encountering non-ascii - filenames, source lines, or exception strings when displaying test results. - Largely contributed by Martin[gz] with some tweaks from Robert Collins. - -* James Westby has supplied test discovery support using the Python 2.7 - TestRunner in testtools.run. This requires the 'discover' module. This - closes bug #250764. - -* Python 3.1 is now supported, thanks to Martin[gz] for a partial patch. - This fixes bug #592375. - -* TestCase.addCleanup has had its docstring corrected about when cleanups run. - -* TestCase.skip is now deprecated in favour of TestCase.skipTest, which is the - Python2.7 spelling for skip. This closes bug #560436. - -* Tests work on IronPython patch from Martin[gz] applied. - -* Thanks to a patch from James Westby testtools.matchers.Mismatch can now - supply a get_details method, which assertThat will query to provide - additional attachments. This can be used to provide additional detail - about the mismatch that doesn't suite being included in describe(). For - instance, if the match process was complex, a log of the process could be - included, permitting debugging. - -* testtools.testresults.real._StringException will now answer __str__ if its - value is unicode by encoding with UTF8, and vice versa to answer __unicode__. - This permits subunit decoded exceptions to contain unicode and still format - correctly. - -0.9.3 -~~~~~ - -More matchers, Python 2.4 support, faster test cloning by switching to copy -rather than deepcopy and better output when exceptions occur in cleanups are -the defining characteristics of this release. - -Improvements ------------- - -* New matcher "Annotate" that adds a simple string message to another matcher, - much like the option 'message' parameter to standard library assertFoo - methods. - -* New matchers "Not" and "MatchesAll". "Not" will invert another matcher, and - "MatchesAll" that needs a successful match for all of its arguments. - -* On Python 2.4, where types.FunctionType cannot be deepcopied, testtools will - now monkeypatch copy._deepcopy_dispatch using the same trivial patch that - added such support to Python 2.5. The monkey patch is triggered by the - absence of FunctionType from the dispatch dict rather than a version check. - Bug #498030. - -* On windows the test 'test_now_datetime_now' should now work reliably. - -* TestCase.getUniqueInteger and TestCase.getUniqueString now have docstrings. - -* TestCase.getUniqueString now takes an optional prefix parameter, so you can - now use it in circumstances that forbid strings with '.'s, and such like. - -* testtools.testcase.clone_test_with_new_id now uses copy.copy, rather than - copy.deepcopy. Tests that need a deeper copy should use the copy protocol to - control how they are copied. Bug #498869. - -* The backtrace test result output tests should now pass on windows and other - systems where os.sep is not '/'. - -* When a cleanUp or tearDown exception occurs, it is now accumulated as a new - traceback in the test details, rather than as a separate call to addError / - addException. This makes testtools work better with most TestResult objects - and fixes bug #335816. - - -0.9.2 -~~~~~ - -Python 3 support, more matchers and better consistency with Python 2.7 -- -you'd think that would be enough for a point release. Well, we here on the -testtools project think that you deserve more. - -We've added a hook so that user code can be called just-in-time whenever there -is an exception, and we've also factored out the "run" logic of test cases so -that new outcomes can be added without fiddling with the actual flow of logic. - -It might sound like small potatoes, but it's changes like these that will -bring about the end of test frameworks. - - -Improvements ------------- - -* A failure in setUp and tearDown now report as failures not as errors. - -* Cleanups now run after tearDown to be consistent with Python 2.7's cleanup - feature. - -* ExtendedToOriginalDecorator now passes unrecognised attributes through - to the decorated result object, permitting other extensions to the - TestCase -> TestResult protocol to work. - -* It is now possible to trigger code just-in-time after an exception causes - a test outcome such as failure or skip. See the testtools MANUAL or - ``pydoc testtools.TestCase.addOnException``. (bug #469092) - -* New matcher Equals which performs a simple equality test. - -* New matcher MatchesAny which looks for a match of any of its arguments. - -* TestCase no longer breaks if a TestSkipped exception is raised with no - parameters. - -* TestCase.run now clones test cases before they are run and runs the clone. - This reduces memory footprint in large test runs - state accumulated on - test objects during their setup and execution gets freed when test case - has finished running unless the TestResult object keeps a reference. - NOTE: As test cloning uses deepcopy, this can potentially interfere if - a test suite has shared state (such as the testscenarios or testresources - projects use). Use the __deepcopy__ hook to control the copying of such - objects so that the shared references stay shared. - -* Testtools now accepts contributions without copyright assignment under some - circumstances. See HACKING for details. - -* Testtools now provides a convenient way to run a test suite using the - testtools result object: python -m testtools.run testspec [testspec...]. - -* Testtools now works on Python 3, thanks to Benjamin Peterson. - -* Test execution now uses a separate class, testtools.RunTest to run single - tests. This can be customised and extended in a more consistent fashion than - the previous run method idiom. See pydoc for more information. - -* The test doubles that testtools itself uses are now available as part of - the testtools API in testtols.testresult.doubles. - -* TracebackContent now sets utf8 as the charset encoding, rather than not - setting one and encoding with the default encoder. - -* With python2.7 testtools.TestSkipped will be the unittest.case.SkipTest - exception class making skips compatible with code that manually raises the - standard library exception. (bug #490109) - -Changes -------- - -* TestCase.getUniqueInteger is now implemented using itertools.count. Thanks - to Benjamin Peterson for the patch. (bug #490111) - - -0.9.1 -~~~~~ - -The new matcher API introduced in 0.9.0 had a small flaw where the matchee -would be evaluated twice to get a description of the mismatch. This could lead -to bugs if the act of matching caused side effects to occur in the matchee. -Since having such side effects isn't desirable, we have changed the API now -before it has become widespread. - -Changes -------- - -* Matcher API changed to avoid evaluating matchee twice. Please consult - the API documentation. - -* TestCase.getUniqueString now uses the test id, not the test method name, - which works nicer with parameterised tests. - -Improvements ------------- - -* Python2.4 is now supported again. - - -0.9.0 -~~~~~ - -This release of testtools is perhaps the most interesting and exciting one -it's ever had. We've continued in bringing together the best practices of unit -testing from across a raft of different Python projects, but we've also -extended our mission to incorporating unit testing concepts from other -languages and from our own research, led by Robert Collins. - -We now support skipping and expected failures. We'll make sure that you -up-call setUp and tearDown, avoiding unexpected testing weirdnesses. We're -now compatible with Python 2.5, 2.6 and 2.7 unittest library. - -All in all, if you are serious about unit testing and want to get the best -thinking from the whole Python community, you should get this release. - -Improvements ------------- - -* A new TestResult API has been added for attaching details to test outcomes. - This API is currently experimental, but is being prepared with the intent - of becoming an upstream Python API. For more details see pydoc - testtools.TestResult and the TestCase addDetail / getDetails methods. - -* assertThat has been added to TestCase. This new assertion supports - a hamcrest-inspired matching protocol. See pydoc testtools.Matcher for - details about writing matchers, and testtools.matchers for the included - matchers. See http://code.google.com/p/hamcrest/. - -* Compatible with Python 2.6 and Python 2.7 - -* Failing to upcall in setUp or tearDown will now cause a test failure. - While the base methods do nothing, failing to upcall is usually a problem - in deeper hierarchies, and checking that the root method is called is a - simple way to catch this common bug. - -* New TestResult decorator ExtendedToOriginalDecorator which handles - downgrading extended API calls like addSkip to older result objects that - do not support them. This is used internally to make testtools simpler but - can also be used to simplify other code built on or for use with testtools. - -* New TextTestResult supporting the extended APIs that testtools provides. - -* Nose will no longer find 'runTest' tests in classes derived from - testtools.testcase.TestCase (bug #312257). - -* Supports the Python 2.7/3.1 addUnexpectedSuccess and addExpectedFailure - TestResult methods, with a support function 'knownFailure' to let tests - trigger these outcomes. - -* When using the skip feature with TestResult objects that do not support it - a test success will now be reported. Previously an error was reported but - production experience has shown that this is too disruptive for projects that - are using skips: they cannot get a clean run on down-level result objects. - - -.. _testtools: http://pypi.python.org/pypi/testtools diff --git a/lib/testtools/README b/lib/testtools/README deleted file mode 100644 index dbc685b38a3..00000000000 --- a/lib/testtools/README +++ /dev/null @@ -1,89 +0,0 @@ -========= -testtools -========= - -testtools is a set of extensions to the Python standard library's unit testing -framework. - -These extensions have been derived from years of experience with unit testing -in Python and come from many different sources. - - -Documentation -------------- - -If you would like to learn more about testtools, consult our documentation in -the 'doc/' directory. You might like to start at 'doc/overview.rst' or -'doc/for-test-authors.rst'. - - -Licensing ---------- - -This project is distributed under the MIT license and copyright is owned by -Jonathan M. Lange and the testtools authors. See LICENSE for details. - -Some code in 'testtools/run.py' is taken from Python's unittest module, and is -copyright Steve Purcell and the Python Software Foundation, it is distributed -under the same license as Python, see LICENSE for details. - - -Required Dependencies ---------------------- - - * Python 2.6+ or 3.0+ - -If you would like to use testtools for earlier Python's, please use testtools -0.9.15. - - -Optional Dependencies ---------------------- - -If you would like to use our undocumented, unsupported Twisted support, then -you will need Twisted. - -If you want to use ``fixtures`` then you can either install fixtures (e.g. from -https://launchpad.net/python-fixtures or http://pypi.python.org/pypi/fixtures) -or alternatively just make sure your fixture objects obey the same protocol. - - -Bug reports and patches ------------------------ - -Please report bugs using Launchpad at . -Patches can also be submitted via Launchpad, or mailed to the author. You can -mail the author directly at jml@mumak.net. - -There's no mailing list for this project yet, however the testing-in-python -mailing list may be a useful resource: - - * Address: testing-in-python@lists.idyll.org - * Subscription link: http://lists.idyll.org/listinfo/testing-in-python - - -History -------- - -testtools used to be called 'pyunit3k'. The name was changed to avoid -conflating the library with the Python 3.0 release (commonly referred to as -'py3k'). - - -Thanks ------- - - * Canonical Ltd - * Bazaar - * Twisted Matrix Labs - * Robert Collins - * Andrew Bennetts - * Benjamin Peterson - * Jamu Kakar - * James Westby - * Martin [gz] - * Michael Hudson-Doyle - * Aaron Bentley - * Christian Kampka - * Gavin Panella - * Martin Pool diff --git a/lib/testtools/doc/Makefile b/lib/testtools/doc/Makefile deleted file mode 100644 index b5d07af57f2..00000000000 --- a/lib/testtools/doc/Makefile +++ /dev/null @@ -1,89 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/testtools.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/testtools.qhc" - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ - "run these through (pdf)latex." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/lib/testtools/doc/_static/placeholder.txt b/lib/testtools/doc/_static/placeholder.txt deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/lib/testtools/doc/_templates/placeholder.txt b/lib/testtools/doc/_templates/placeholder.txt deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/lib/testtools/doc/conf.py b/lib/testtools/doc/conf.py deleted file mode 100644 index de5fdd4224e..00000000000 --- a/lib/testtools/doc/conf.py +++ /dev/null @@ -1,194 +0,0 @@ -# -*- coding: utf-8 -*- -# -# testtools documentation build configuration file, created by -# sphinx-quickstart on Sun Nov 28 13:45:40 2010. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.append(os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'testtools' -copyright = u'2010, The testtools authors' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = 'VERSION' -# The full version, including alpha/beta/rc tags. -release = 'VERSION' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of documents that shouldn't be included in the build. -#unused_docs = [] - -# List of directories, relative to source directory, that shouldn't be searched -# for source files. -exclude_trees = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. Major themes that come with -# Sphinx are currently 'default' and 'sphinxdoc'. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_use_modindex = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = '' - -# Output file base name for HTML help builder. -htmlhelp_basename = 'testtoolsdoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'testtools.tex', u'testtools Documentation', - u'The testtools authors', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_use_modindex = True diff --git a/lib/testtools/doc/for-framework-folk.rst b/lib/testtools/doc/for-framework-folk.rst deleted file mode 100644 index ecc11f38e9a..00000000000 --- a/lib/testtools/doc/for-framework-folk.rst +++ /dev/null @@ -1,238 +0,0 @@ -============================ -testtools for framework folk -============================ - -Introduction -============ - -In addition to having many features :doc:`for test authors -`, testtools also has many bits and pieces that are useful -for folk who write testing frameworks. - -If you are the author of a test runner, are working on a very large -unit-tested project, are trying to get one testing framework to play nicely -with another or are hacking away at getting your test suite to run in parallel -over a heterogenous cluster of machines, this guide is for you. - -This manual is a summary. You can get details by consulting the `testtools -API docs`_. - - -Extensions to TestCase -====================== - -Custom exception handling -------------------------- - -testtools provides a way to control how test exceptions are handled. To do -this, add a new exception to ``self.exception_handlers`` on a -``testtools.TestCase``. For example:: - - >>> self.exception_handlers.insert(-1, (ExceptionClass, handler)). - -Having done this, if any of ``setUp``, ``tearDown``, or the test method raise -``ExceptionClass``, ``handler`` will be called with the test case, test result -and the raised exception. - -Use this if you want to add a new kind of test result, that is, if you think -that ``addError``, ``addFailure`` and so forth are not enough for your needs. - - -Controlling test execution --------------------------- - -If you want to control more than just how exceptions are raised, you can -provide a custom ``RunTest`` to a ``TestCase``. The ``RunTest`` object can -change everything about how the test executes. - -To work with ``testtools.TestCase``, a ``RunTest`` must have a factory that -takes a test and an optional list of exception handlers. Instances returned -by the factory must have a ``run()`` method that takes an optional ``TestResult`` -object. - -The default is ``testtools.runtest.RunTest``, which calls ``setUp``, the test -method, ``tearDown`` and clean ups (see :ref:`addCleanup`) in the normal, vanilla -way that Python's standard unittest_ does. - -To specify a ``RunTest`` for all the tests in a ``TestCase`` class, do something -like this:: - - class SomeTests(TestCase): - run_tests_with = CustomRunTestFactory - -To specify a ``RunTest`` for a specific test in a ``TestCase`` class, do:: - - class SomeTests(TestCase): - @run_test_with(CustomRunTestFactory, extra_arg=42, foo='whatever') - def test_something(self): - pass - -In addition, either of these can be overridden by passing a factory in to the -``TestCase`` constructor with the optional ``runTest`` argument. - - -Test renaming -------------- - -``testtools.clone_test_with_new_id`` is a function to copy a test case -instance to one with a new name. This is helpful for implementing test -parameterization. - - -Test placeholders -================= - -Sometimes, it's useful to be able to add things to a test suite that are not -actually tests. For example, you might wish to represents import failures -that occur during test discovery as tests, so that your test result object -doesn't have to do special work to handle them nicely. - -testtools provides two such objects, called "placeholders": ``PlaceHolder`` -and ``ErrorHolder``. ``PlaceHolder`` takes a test id and an optional -description. When it's run, it succeeds. ``ErrorHolder`` takes a test id, -and error and an optional short description. When it's run, it reports that -error. - -These placeholders are best used to log events that occur outside the test -suite proper, but are still very relevant to its results. - -e.g.:: - - >>> suite = TestSuite() - >>> suite.add(PlaceHolder('I record an event')) - >>> suite.run(TextTestResult(verbose=True)) - I record an event [OK] - - -Extensions to TestResult -======================== - -TestResult.addSkip ------------------- - -This method is called on result objects when a test skips. The -``testtools.TestResult`` class records skips in its ``skip_reasons`` instance -dict. The can be reported on in much the same way as succesful tests. - - -TestResult.time ---------------- - -This method controls the time used by a ``TestResult``, permitting accurate -timing of test results gathered on different machines or in different threads. -See pydoc testtools.TestResult.time for more details. - - -ThreadsafeForwardingResult --------------------------- - -A ``TestResult`` which forwards activity to another test result, but synchronises -on a semaphore to ensure that all the activity for a single test arrives in a -batch. This allows simple TestResults which do not expect concurrent test -reporting to be fed the activity from multiple test threads, or processes. - -Note that when you provide multiple errors for a single test, the target sees -each error as a distinct complete test. - - -MultiTestResult ---------------- - -A test result that dispatches its events to many test results. Use this -to combine multiple different test result objects into one test result object -that can be passed to ``TestCase.run()`` or similar. For example:: - - a = TestResult() - b = TestResult() - combined = MultiTestResult(a, b) - combined.startTestRun() # Calls a.startTestRun() and b.startTestRun() - -Each of the methods on ``MultiTestResult`` will return a tuple of whatever the -component test results return. - - -TestResultDecorator -------------------- - -Not strictly a ``TestResult``, but something that implements the extended -``TestResult`` interface of testtools. It can be subclassed to create objects -that wrap ``TestResults``. - - -TextTestResult --------------- - -A ``TestResult`` that provides a text UI very similar to the Python standard -library UI. Key differences are that its supports the extended outcomes and -details API, and is completely encapsulated into the result object, permitting -it to be used without a 'TestRunner' object. Not all the Python 2.7 outcomes -are displayed (yet). It is also a 'quiet' result with no dots or verbose mode. -These limitations will be corrected soon. - - -ExtendedToOriginalDecorator ---------------------------- - -Adapts legacy ``TestResult`` objects, such as those found in older Pythons, to -meet the testtools ``TestResult`` API. - - -Test Doubles ------------- - -In testtools.testresult.doubles there are three test doubles that testtools -uses for its own testing: ``Python26TestResult``, ``Python27TestResult``, -``ExtendedTestResult``. These TestResult objects implement a single variation of -the TestResult API each, and log activity to a list ``self._events``. These are -made available for the convenience of people writing their own extensions. - - -startTestRun and stopTestRun ----------------------------- - -Python 2.7 added hooks ``startTestRun`` and ``stopTestRun`` which are called -before and after the entire test run. 'stopTestRun' is particularly useful for -test results that wish to produce summary output. - -``testtools.TestResult`` provides default ``startTestRun`` and ``stopTestRun`` -methods, and he default testtools runner will call these methods -appropriately. - -The ``startTestRun`` method will reset any errors, failures and so forth on -the result, making the result object look as if no tests have been run. - - -Extensions to TestSuite -======================= - -ConcurrentTestSuite -------------------- - -A TestSuite for parallel testing. This is used in conjuction with a helper that -runs a single suite in some parallel fashion (for instance, forking, handing -off to a subprocess, to a compute cloud, or simple threads). -ConcurrentTestSuite uses the helper to get a number of separate runnable -objects with a run(result), runs them all in threads using the -ThreadsafeForwardingResult to coalesce their activity. - -FixtureSuite ------------- - -A test suite that sets up a fixture_ before running any tests, and then tears -it down after all of the tests are run. The fixture is *not* made available to -any of the tests. - -sorted_tests ------------- - -Given the composite structure of TestSuite / TestCase, sorting tests is -problematic - you can't tell what functionality is embedded into custom Suite -implementations. In order to deliver consistent test orders when using test -discovery (see http://bugs.python.org/issue16709), testtools flattens and -sorts tests that have the standard TestSuite, defines a new method sort_tests, -which can be used by non-standard TestSuites to know when they should sort -their tests. - -.. _`testtools API docs`: http://mumak.net/testtools/apidocs/ -.. _unittest: http://docs.python.org/library/unittest.html -.. _fixture: http://pypi.python.org/pypi/fixtures diff --git a/lib/testtools/doc/for-test-authors.rst b/lib/testtools/doc/for-test-authors.rst deleted file mode 100644 index c9e6c6adc7d..00000000000 --- a/lib/testtools/doc/for-test-authors.rst +++ /dev/null @@ -1,1360 +0,0 @@ -========================== -testtools for test authors -========================== - -If you are writing tests for a Python project and you (rather wisely) want to -use testtools to do so, this is the manual for you. - -We assume that you already know Python and that you know something about -automated testing already. - -If you are a test author of an unusually large or unusually unusual test -suite, you might be interested in :doc:`for-framework-folk`. - -You might also be interested in the `testtools API docs`_. - - -Introduction -============ - -testtools is a set of extensions to Python's standard unittest module. -Writing tests with testtools is very much like writing tests with standard -Python, or with Twisted's "trial_", or nose_, except a little bit easier and -more enjoyable. - -Below, we'll try to give some examples of how to use testtools in its most -basic way, as well as a sort of feature-by-feature breakdown of the cool bits -that you could easily miss. - - -The basics -========== - -Here's what a basic testtools unit tests look like:: - - from testtools import TestCase - from myproject import silly - - class TestSillySquare(TestCase): - """Tests for silly square function.""" - - def test_square(self): - # 'square' takes a number and multiplies it by itself. - result = silly.square(7) - self.assertEqual(result, 49) - - def test_square_bad_input(self): - # 'square' raises a TypeError if it's given bad input, say a - # string. - self.assertRaises(TypeError, silly.square, "orange") - - -Here you have a class that inherits from ``testtools.TestCase`` and bundles -together a bunch of related tests. The tests themselves are methods on that -class that begin with ``test_``. - -Running your tests ------------------- - -You can run these tests in many ways. testtools provides a very basic -mechanism for doing so:: - - $ python -m testtools.run exampletest - Tests running... - Ran 2 tests in 0.000s - - OK - -where 'exampletest' is a module that contains unit tests. By default, -``testtools.run`` will *not* recursively search the module or package for unit -tests. To do this, you will need to either have the discover_ module -installed or have Python 2.7 or later, and then run:: - - $ python -m testtools.run discover packagecontainingtests - -For more information see the Python 2.7 unittest documentation, or:: - - python -m testtools.run --help - -As your testing needs grow and evolve, you will probably want to use a more -sophisticated test runner. There are many of these for Python, and almost all -of them will happily run testtools tests. In particular: - -* testrepository_ -* Trial_ -* nose_ -* unittest2_ -* `zope.testrunner`_ (aka zope.testing) - -From now on, we'll assume that you know how to run your tests. - -Running test with Distutils -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you are using Distutils_ to build your Python project, you can use the testtools -Distutils_ command to integrate testtools into your Distutils_ workflow:: - - from distutils.core import setup - from testtools import TestCommand - setup(name='foo', - version='1.0', - py_modules=['foo'], - cmdclass={'test': TestCommand} - ) - -You can then run:: - - $ python setup.py test -m exampletest - Tests running... - Ran 2 tests in 0.000s - - OK - -For more information about the capabilities of the `TestCommand` command see:: - - $ python setup.py test --help - -You can use the `setup configuration`_ to specify the default behavior of the -`TestCommand` command. - -Assertions -========== - -The core of automated testing is making assertions about the way things are, -and getting a nice, helpful, informative error message when things are not as -they ought to be. - -All of the assertions that you can find in Python standard unittest_ can be -found in testtools (remember, testtools extends unittest). testtools changes -the behaviour of some of those assertions slightly and adds some new -assertions that you will almost certainly find useful. - - -Improved assertRaises ---------------------- - -``TestCase.assertRaises`` returns the caught exception. This is useful for -asserting more things about the exception than just the type:: - - def test_square_bad_input(self): - # 'square' raises a TypeError if it's given bad input, say a - # string. - e = self.assertRaises(TypeError, silly.square, "orange") - self.assertEqual("orange", e.bad_value) - self.assertEqual("Cannot square 'orange', not a number.", str(e)) - -Note that this is incompatible with the ``assertRaises`` in unittest2 and -Python2.7. - - -ExpectedException ------------------ - -If you are using a version of Python that supports the ``with`` context -manager syntax, you might prefer to use that syntax to ensure that code raises -particular errors. ``ExpectedException`` does just that. For example:: - - def test_square_root_bad_input_2(self): - # 'square' raises a TypeError if it's given bad input. - with ExpectedException(TypeError, "Cannot square.*"): - silly.square('orange') - -The first argument to ``ExpectedException`` is the type of exception you -expect to see raised. The second argument is optional, and can be either a -regular expression or a matcher. If it is a regular expression, the ``str()`` -of the raised exception must match the regular expression. If it is a matcher, -then the raised exception object must match it. - - -assertIn, assertNotIn ---------------------- - -These two assertions check whether a value is in a sequence and whether a -value is not in a sequence. They are "assert" versions of the ``in`` and -``not in`` operators. For example:: - - def test_assert_in_example(self): - self.assertIn('a', 'cat') - self.assertNotIn('o', 'cat') - self.assertIn(5, list_of_primes_under_ten) - self.assertNotIn(12, list_of_primes_under_ten) - - -assertIs, assertIsNot ---------------------- - -These two assertions check whether values are identical to one another. This -is sometimes useful when you want to test something more strict than mere -equality. For example:: - - def test_assert_is_example(self): - foo = [None] - foo_alias = foo - bar = [None] - self.assertIs(foo, foo_alias) - self.assertIsNot(foo, bar) - self.assertEqual(foo, bar) # They are equal, but not identical - - -assertIsInstance ----------------- - -As much as we love duck-typing and polymorphism, sometimes you need to check -whether or not a value is of a given type. This method does that. For -example:: - - def test_assert_is_instance_example(self): - now = datetime.now() - self.assertIsInstance(now, datetime) - -Note that there is no ``assertIsNotInstance`` in testtools currently. - - -expectFailure -------------- - -Sometimes it's useful to write tests that fail. For example, you might want -to turn a bug report into a unit test, but you don't know how to fix the bug -yet. Or perhaps you want to document a known, temporary deficiency in a -dependency. - -testtools gives you the ``TestCase.expectFailure`` to help with this. You use -it to say that you expect this assertion to fail. When the test runs and the -assertion fails, testtools will report it as an "expected failure". - -Here's an example:: - - def test_expect_failure_example(self): - self.expectFailure( - "cats should be dogs", self.assertEqual, 'cats', 'dogs') - -As long as 'cats' is not equal to 'dogs', the test will be reported as an -expected failure. - -If ever by some miracle 'cats' becomes 'dogs', then testtools will report an -"unexpected success". Unlike standard unittest, testtools treats this as -something that fails the test suite, like an error or a failure. - - -Matchers -======== - -The built-in assertion methods are very useful, they are the bread and butter -of writing tests. However, soon enough you will probably want to write your -own assertions. Perhaps there are domain specific things that you want to -check (e.g. assert that two widgets are aligned parallel to the flux grid), or -perhaps you want to check something that could almost but not quite be found -in some other standard library (e.g. assert that two paths point to the same -file). - -When you are in such situations, you could either make a base class for your -project that inherits from ``testtools.TestCase`` and make sure that all of -your tests derive from that, *or* you could use the testtools ``Matcher`` -system. - - -Using Matchers --------------- - -Here's a really basic example using stock matchers found in testtools:: - - import testtools - from testtools.matchers import Equals - - class TestSquare(TestCase): - def test_square(self): - result = square(7) - self.assertThat(result, Equals(49)) - -The line ``self.assertThat(result, Equals(49))`` is equivalent to -``self.assertEqual(result, 49)`` and means "assert that ``result`` equals 49". -The difference is that ``assertThat`` is a more general method that takes some -kind of observed value (in this case, ``result``) and any matcher object -(here, ``Equals(49)``). - -The matcher object could be absolutely anything that implements the Matcher -protocol. This means that you can make more complex matchers by combining -existing ones:: - - def test_square_silly(self): - result = square(7) - self.assertThat(result, Not(Equals(50))) - -Which is roughly equivalent to:: - - def test_square_silly(self): - result = square(7) - self.assertNotEqual(result, 50) - - -Stock matchers --------------- - -testtools comes with many matchers built in. They can all be found in and -imported from the ``testtools.matchers`` module. - -Equals -~~~~~~ - -Matches if two items are equal. For example:: - - def test_equals_example(self): - self.assertThat([42], Equals([42])) - - -Is -~~~ - -Matches if two items are identical. For example:: - - def test_is_example(self): - foo = object() - self.assertThat(foo, Is(foo)) - - -IsInstance -~~~~~~~~~~ - -Adapts isinstance() to use as a matcher. For example:: - - def test_isinstance_example(self): - class MyClass:pass - self.assertThat(MyClass(), IsInstance(MyClass)) - self.assertThat(MyClass(), IsInstance(MyClass, str)) - - -The raises helper -~~~~~~~~~~~~~~~~~ - -Matches if a callable raises a particular type of exception. For example:: - - def test_raises_example(self): - self.assertThat(lambda: 1/0, raises(ZeroDivisionError)) - -This is actually a convenience function that combines two other matchers: -Raises_ and MatchesException_. - - -DocTestMatches -~~~~~~~~~~~~~~ - -Matches a string as if it were the output of a doctest_ example. Very useful -for making assertions about large chunks of text. For example:: - - import doctest - - def test_doctest_example(self): - output = "Colorless green ideas" - self.assertThat( - output, - DocTestMatches("Colorless ... ideas", doctest.ELLIPSIS)) - -We highly recommend using the following flags:: - - doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | doctest.REPORT_NDIFF - - -GreaterThan -~~~~~~~~~~~ - -Matches if the given thing is greater than the thing in the matcher. For -example:: - - def test_greater_than_example(self): - self.assertThat(3, GreaterThan(2)) - - -LessThan -~~~~~~~~ - -Matches if the given thing is less than the thing in the matcher. For -example:: - - def test_less_than_example(self): - self.assertThat(2, LessThan(3)) - - -StartsWith, EndsWith -~~~~~~~~~~~~~~~~~~~~ - -These matchers check to see if a string starts with or ends with a particular -substring. For example:: - - def test_starts_and_ends_with_example(self): - self.assertThat('underground', StartsWith('und')) - self.assertThat('underground', EndsWith('und')) - - -Contains -~~~~~~~~ - -This matcher checks to see if the given thing contains the thing in the -matcher. For example:: - - def test_contains_example(self): - self.assertThat('abc', Contains('b')) - - -MatchesException -~~~~~~~~~~~~~~~~ - -Matches an exc_info tuple if the exception is of the correct type. For -example:: - - def test_matches_exception_example(self): - try: - raise RuntimeError('foo') - except RuntimeError: - exc_info = sys.exc_info() - self.assertThat(exc_info, MatchesException(RuntimeError)) - self.assertThat(exc_info, MatchesException(RuntimeError('bar')) - -Most of the time, you will want to uses `The raises helper`_ instead. - - -NotEquals -~~~~~~~~~ - -Matches if something is not equal to something else. Note that this is subtly -different to ``Not(Equals(x))``. ``NotEquals(x)`` will match if ``y != x``, -``Not(Equals(x))`` will match if ``not y == x``. - -You only need to worry about this distinction if you are testing code that -relies on badly written overloaded equality operators. - - -KeysEqual -~~~~~~~~~ - -Matches if the keys of one dict are equal to the keys of another dict. For -example:: - - def test_keys_equal(self): - x = {'a': 1, 'b': 2} - y = {'a': 2, 'b': 3} - self.assertThat(x, KeysEqual(y)) - - -MatchesRegex -~~~~~~~~~~~~ - -Matches a string against a regular expression, which is a wonderful thing to -be able to do, if you think about it:: - - def test_matches_regex_example(self): - self.assertThat('foo', MatchesRegex('fo+')) - - -File- and path-related matchers -------------------------------- - -testtools also has a number of matchers to help with asserting things about -the state of the filesystem. - -PathExists -~~~~~~~~~~ - -Matches if a path exists:: - - self.assertThat('/', PathExists()) - - -DirExists -~~~~~~~~~ - -Matches if a path exists and it refers to a directory:: - - # This will pass on most Linux systems. - self.assertThat('/home/', DirExists()) - # This will not - self.assertThat('/home/jml/some-file.txt', DirExists()) - - -FileExists -~~~~~~~~~~ - -Matches if a path exists and it refers to a file (as opposed to a directory):: - - # This will pass on most Linux systems. - self.assertThat('/bin/true', FileExists()) - # This will not. - self.assertThat('/home/', FileExists()) - - -DirContains -~~~~~~~~~~~ - -Matches if the given directory contains the specified files and directories. -Say we have a directory ``foo`` that has the files ``a``, ``b`` and ``c``, -then:: - - self.assertThat('foo', DirContains(['a', 'b', 'c'])) - -will match, but:: - - self.assertThat('foo', DirContains(['a', 'b'])) - -will not. - -The matcher sorts both the input and the list of names we get back from the -filesystem. - -You can use this in a more advanced way, and match the sorted directory -listing against an arbitrary matcher:: - - self.assertThat('foo', DirContains(matcher=Contains('a'))) - - -FileContains -~~~~~~~~~~~~ - -Matches if the given file has the specified contents. Say there's a file -called ``greetings.txt`` with the contents, ``Hello World!``:: - - self.assertThat('greetings.txt', FileContains("Hello World!")) - -will match. - -You can also use this in a more advanced way, and match the contents of the -file against an arbitrary matcher:: - - self.assertThat('greetings.txt', FileContains(matcher=Contains('!'))) - - -HasPermissions -~~~~~~~~~~~~~~ - -Used for asserting that a file or directory has certain permissions. Uses -octal-mode permissions for both input and matching. For example:: - - self.assertThat('/tmp', HasPermissions('1777')) - self.assertThat('id_rsa', HasPermissions('0600')) - -This is probably more useful on UNIX systems than on Windows systems. - - -SamePath -~~~~~~~~ - -Matches if two paths actually refer to the same thing. The paths don't have -to exist, but if they do exist, ``SamePath`` will resolve any symlinks.:: - - self.assertThat('somefile', SamePath('childdir/../somefile')) - - -TarballContains -~~~~~~~~~~~~~~~ - -Matches the contents of a tarball. In many ways, much like ``DirContains``, -but instead of matching on ``os.listdir`` matches on ``TarFile.getnames``. - - -Combining matchers ------------------- - -One great thing about matchers is that you can readily combine existing -matchers to get variations on their behaviour or to quickly build more complex -assertions. - -Below are a few of the combining matchers that come with testtools. - - -Not -~~~ - -Negates another matcher. For example:: - - def test_not_example(self): - self.assertThat([42], Not(Equals("potato"))) - self.assertThat([42], Not(Is([42]))) - -If you find yourself using ``Not`` frequently, you may wish to create a custom -matcher for it. For example:: - - IsNot = lambda x: Not(Is(x)) - - def test_not_example_2(self): - self.assertThat([42], IsNot([42])) - - -Annotate -~~~~~~~~ - -Used to add custom notes to a matcher. For example:: - - def test_annotate_example(self): - result = 43 - self.assertThat( - result, Annotate("Not the answer to the Question!", Equals(42)) - -Since the annotation is only ever displayed when there is a mismatch -(e.g. when ``result`` does not equal 42), it's a good idea to phrase the note -negatively, so that it describes what a mismatch actually means. - -As with Not_, you may wish to create a custom matcher that describes a -common operation. For example:: - - PoliticallyEquals = lambda x: Annotate("Death to the aristos!", Equals(x)) - - def test_annotate_example_2(self): - self.assertThat("orange", PoliticallyEquals("yellow")) - -You can have assertThat perform the annotation for you as a convenience:: - - def test_annotate_example_3(self): - self.assertThat("orange", Equals("yellow"), "Death to the aristos!") - - -AfterPreprocessing -~~~~~~~~~~~~~~~~~~ - -Used to make a matcher that applies a function to the matched object before -matching. This can be used to aid in creating trivial matchers as functions, for -example:: - - def test_after_preprocessing_example(self): - def HasFileContent(content): - def _read(path): - return open(path).read() - return AfterPreprocessing(_read, Equals(content)) - self.assertThat('/tmp/foo.txt', PathHasFileContent("Hello world!")) - - -MatchesAll -~~~~~~~~~~ - -Combines many matchers to make a new matcher. The new matcher will only match -things that match every single one of the component matchers. - -It's much easier to understand in Python than in English:: - - def test_matches_all_example(self): - has_und_at_both_ends = MatchesAll(StartsWith("und"), EndsWith("und")) - # This will succeed. - self.assertThat("underground", has_und_at_both_ends) - # This will fail. - self.assertThat("found", has_und_at_both_ends) - # So will this. - self.assertThat("undead", has_und_at_both_ends) - -At this point some people ask themselves, "why bother doing this at all? why -not just have two separate assertions?". It's a good question. - -The first reason is that when a ``MatchesAll`` gets a mismatch, the error will -include information about all of the bits that mismatched. When you have two -separate assertions, as below:: - - def test_two_separate_assertions(self): - self.assertThat("foo", StartsWith("und")) - self.assertThat("foo", EndsWith("und")) - -Then you get absolutely no information from the second assertion if the first -assertion fails. Tests are largely there to help you debug code, so having -more information in error messages is a big help. - -The second reason is that it is sometimes useful to give a name to a set of -matchers. ``has_und_at_both_ends`` is a bit contrived, of course, but it is -clear. The ``FileExists`` and ``DirExists`` matchers included in testtools -are perhaps better real examples. - -If you want only the first mismatch to be reported, pass ``first_only=True`` -as a keyword parameter to ``MatchesAll``. - - -MatchesAny -~~~~~~~~~~ - -Like MatchesAll_, ``MatchesAny`` combines many matchers to make a new -matcher. The difference is that the new matchers will match a thing if it -matches *any* of the component matchers. - -For example:: - - def test_matches_any_example(self): - self.assertThat(42, MatchesAny(Equals(5), Not(Equals(6)))) - - -AllMatch -~~~~~~~~ - -Matches many values against a single matcher. Can be used to make sure that -many things all meet the same condition:: - - def test_all_match_example(self): - self.assertThat([2, 3, 5, 7], AllMatch(LessThan(10))) - -If the match fails, then all of the values that fail to match will be included -in the error message. - -In some ways, this is the converse of MatchesAll_. - - -MatchesListwise -~~~~~~~~~~~~~~~ - -Where ``MatchesAny`` and ``MatchesAll`` combine many matchers to match a -single value, ``MatchesListwise`` combines many matches to match many values. - -For example:: - - def test_matches_listwise_example(self): - self.assertThat( - [1, 2, 3], MatchesListwise(map(Equals, [1, 2, 3]))) - -This is useful for writing custom, domain-specific matchers. - -If you want only the first mismatch to be reported, pass ``first_only=True`` -to ``MatchesListwise``. - - -MatchesSetwise -~~~~~~~~~~~~~~ - -Combines many matchers to match many values, without regard to their order. - -Here's an example:: - - def test_matches_setwise_example(self): - self.assertThat( - [1, 2, 3], MatchesSetwise(Equals(2), Equals(3), Equals(1))) - -Much like ``MatchesListwise``, best used for writing custom, domain-specific -matchers. - - -MatchesStructure -~~~~~~~~~~~~~~~~ - -Creates a matcher that matches certain attributes of an object against a -pre-defined set of matchers. - -It's much easier to understand in Python than in English:: - - def test_matches_structure_example(self): - foo = Foo() - foo.a = 1 - foo.b = 2 - matcher = MatchesStructure(a=Equals(1), b=Equals(2)) - self.assertThat(foo, matcher) - -Since all of the matchers used were ``Equals``, we could also write this using -the ``byEquality`` helper:: - - def test_matches_structure_example(self): - foo = Foo() - foo.a = 1 - foo.b = 2 - matcher = MatchesStructure.byEquality(a=1, b=2) - self.assertThat(foo, matcher) - -``MatchesStructure.fromExample`` takes an object and a list of attributes and -creates a ``MatchesStructure`` matcher where each attribute of the matched -object must equal each attribute of the example object. For example:: - - matcher = MatchesStructure.fromExample(foo, 'a', 'b') - -is exactly equivalent to ``matcher`` in the previous example. - - -MatchesPredicate -~~~~~~~~~~~~~~~~ - -Sometimes, all you want to do is create a matcher that matches if a given -function returns True, and mismatches if it returns False. - -For example, you might have an ``is_prime`` function and want to make a -matcher based on it:: - - def test_prime_numbers(self): - IsPrime = MatchesPredicate(is_prime, '%s is not prime.') - self.assertThat(7, IsPrime) - self.assertThat(1983, IsPrime) - # This will fail. - self.assertThat(42, IsPrime) - -Which will produce the error message:: - - Traceback (most recent call last): - File "...", line ..., in test_prime_numbers - self.assertThat(42, IsPrime) - MismatchError: 42 is not prime. - - -Raises -~~~~~~ - -Takes whatever the callable raises as an exc_info tuple and matches it against -whatever matcher it was given. For example, if you want to assert that a -callable raises an exception of a given type:: - - def test_raises_example(self): - self.assertThat( - lambda: 1/0, Raises(MatchesException(ZeroDivisionError))) - -Although note that this could also be written as:: - - def test_raises_example_convenient(self): - self.assertThat(lambda: 1/0, raises(ZeroDivisionError)) - -See also MatchesException_ and `the raises helper`_ - - -Writing your own matchers -------------------------- - -Combining matchers is fun and can get you a very long way indeed, but -sometimes you will have to write your own. Here's how. - -You need to make two closely-linked objects: a ``Matcher`` and a -``Mismatch``. The ``Matcher`` knows how to actually make the comparison, and -the ``Mismatch`` knows how to describe a failure to match. - -Here's an example matcher:: - - class IsDivisibleBy(object): - """Match if a number is divisible by another number.""" - def __init__(self, divider): - self.divider = divider - def __str__(self): - return 'IsDivisibleBy(%s)' % (self.divider,) - def match(self, actual): - remainder = actual % self.divider - if remainder != 0: - return IsDivisibleByMismatch(actual, self.divider, remainder) - else: - return None - -The matcher has a constructor that takes parameters that describe what you -actually *expect*, in this case a number that other numbers ought to be -divisible by. It has a ``__str__`` method, the result of which is displayed -on failure by ``assertThat`` and a ``match`` method that does the actual -matching. - -``match`` takes something to match against, here ``actual``, and decides -whether or not it matches. If it does match, then ``match`` must return -``None``. If it does *not* match, then ``match`` must return a ``Mismatch`` -object. ``assertThat`` will call ``match`` and then fail the test if it -returns a non-None value. For example:: - - def test_is_divisible_by_example(self): - # This succeeds, since IsDivisibleBy(5).match(10) returns None. - self.assertThat(10, IsDivisbleBy(5)) - # This fails, since IsDivisibleBy(7).match(10) returns a mismatch. - self.assertThat(10, IsDivisbleBy(7)) - -The mismatch is responsible for what sort of error message the failing test -generates. Here's an example mismatch:: - - class IsDivisibleByMismatch(object): - def __init__(self, number, divider, remainder): - self.number = number - self.divider = divider - self.remainder = remainder - - def describe(self): - return "%r is not divisible by %r, %r remains" % ( - self.number, self.divider, self.remainder) - - def get_details(self): - return {} - -The mismatch takes information about the mismatch, and provides a ``describe`` -method that assembles all of that into a nice error message for end users. -You can use the ``get_details`` method to provide extra, arbitrary data with -the mismatch (e.g. the contents of a log file). Most of the time it's fine to -just return an empty dict. You can read more about Details_ elsewhere in this -document. - -Sometimes you don't need to create a custom mismatch class. In particular, if -you don't care *when* the description is calculated, then you can just do that -in the Matcher itself like this:: - - def match(self, actual): - remainder = actual % self.divider - if remainder != 0: - return Mismatch( - "%r is not divisible by %r, %r remains" % ( - actual, self.divider, remainder)) - else: - return None - -When writing a ``describe`` method or constructing a ``Mismatch`` object the -code should ensure it only emits printable unicode. As this output must be -combined with other text and forwarded for presentation, letting through -non-ascii bytes of ambiguous encoding or control characters could throw an -exception or mangle the display. In most cases simply avoiding the ``%s`` -format specifier and using ``%r`` instead will be enough. For examples of -more complex formatting see the ``testtools.matchers`` implementatons. - - -Details -======= - -As we may have mentioned once or twice already, one of the great benefits of -automated tests is that they help find, isolate and debug errors in your -system. - -Frequently however, the information provided by a mere assertion failure is -not enough. It's often useful to have other information: the contents of log -files; what queries were run; benchmark timing information; what state certain -subsystem components are in and so forth. - -testtools calls all of these things "details" and provides a single, powerful -mechanism for including this information in your test run. - -Here's an example of how to add them:: - - from testtools import TestCase - from testtools.content import text_content - - class TestSomething(TestCase): - - def test_thingy(self): - self.addDetail('arbitrary-color-name', text_content("blue")) - 1 / 0 # Gratuitous error! - -A detail an arbitrary piece of content given a name that's unique within the -test. Here the name is ``arbitrary-color-name`` and the content is -``text_content("blue")``. The name can be any text string, and the content -can be any ``testtools.content.Content`` object. - -When the test runs, testtools will show you something like this:: - - ====================================================================== - ERROR: exampletest.TestSomething.test_thingy - ---------------------------------------------------------------------- - arbitrary-color-name: {{{blue}}} - - Traceback (most recent call last): - File "exampletest.py", line 8, in test_thingy - 1 / 0 # Gratuitous error! - ZeroDivisionError: integer division or modulo by zero - ------------ - Ran 1 test in 0.030s - -As you can see, the detail is included as an attachment, here saying -that our arbitrary-color-name is "blue". - - -Content -------- - -For the actual content of details, testtools uses its own MIME-based Content -object. This allows you to attach any information that you could possibly -conceive of to a test, and allows testtools to use or serialize that -information. - -The basic ``testtools.content.Content`` object is constructed from a -``testtools.content.ContentType`` and a nullary callable that must return an -iterator of chunks of bytes that the content is made from. - -So, to make a Content object that is just a simple string of text, you can -do:: - - from testtools.content import Content - from testtools.content_type import ContentType - - text = Content(ContentType('text', 'plain'), lambda: ["some text"]) - -Because adding small bits of text content is very common, there's also a -convenience method:: - - text = text_content("some text") - -To make content out of an image stored on disk, you could do something like:: - - image = Content(ContentType('image', 'png'), lambda: open('foo.png').read()) - -Or you could use the convenience function:: - - image = content_from_file('foo.png', ContentType('image', 'png')) - -The ``lambda`` helps make sure that the file is opened and the actual bytes -read only when they are needed – by default, when the test is finished. This -means that tests can construct and add Content objects freely without worrying -too much about how they affect run time. - - -A realistic example -------------------- - -A very common use of details is to add a log file to failing tests. Say your -project has a server represented by a class ``SomeServer`` that you can start -up and shut down in tests, but runs in another process. You want to test -interaction with that server, and whenever the interaction fails, you want to -see the client-side error *and* the logs from the server-side. Here's how you -might do it:: - - from testtools import TestCase - from testtools.content import attach_file, Content - from testtools.content_type import UTF8_TEXT - - from myproject import SomeServer - - class SomeTestCase(TestCase): - - def setUp(self): - super(SomeTestCase, self).setUp() - self.server = SomeServer() - self.server.start_up() - self.addCleanup(self.server.shut_down) - self.addCleanup(attach_file, self.server.logfile, self) - - def attach_log_file(self): - self.addDetail( - 'log-file', - Content(UTF8_TEXT, - lambda: open(self.server.logfile, 'r').readlines())) - - def test_a_thing(self): - self.assertEqual("cool", self.server.temperature) - -This test will attach the log file of ``SomeServer`` to each test that is -run. testtools will only display the log file for failing tests, so it's not -such a big deal. - -If the act of adding at detail is expensive, you might want to use -addOnException_ so that you only do it when a test actually raises an -exception. - - -Controlling test execution -========================== - -.. _addCleanup: - -addCleanup ----------- - -``TestCase.addCleanup`` is a robust way to arrange for a clean up function to -be called before ``tearDown``. This is a powerful and simple alternative to -putting clean up logic in a try/finally block or ``tearDown`` method. For -example:: - - def test_foo(self): - foo.lock() - self.addCleanup(foo.unlock) - ... - -This is particularly useful if you have some sort of factory in your test:: - - def make_locked_foo(self): - foo = Foo() - foo.lock() - self.addCleanup(foo.unlock) - return foo - - def test_frotz_a_foo(self): - foo = self.make_locked_foo() - foo.frotz() - self.assertEqual(foo.frotz_count, 1) - -Any extra arguments or keyword arguments passed to ``addCleanup`` are passed -to the callable at cleanup time. - -Cleanups can also report multiple errors, if appropriate by wrapping them in -a ``testtools.MultipleExceptions`` object:: - - raise MultipleExceptions(exc_info1, exc_info2) - - -Fixtures --------- - -Tests often depend on a system being set up in a certain way, or having -certain resources available to them. Perhaps a test needs a connection to the -database or access to a running external server. - -One common way of doing this is to do:: - - class SomeTest(TestCase): - def setUp(self): - super(SomeTest, self).setUp() - self.server = Server() - self.server.setUp() - self.addCleanup(self.server.tearDown) - -testtools provides a more convenient, declarative way to do the same thing:: - - class SomeTest(TestCase): - def setUp(self): - super(SomeTest, self).setUp() - self.server = self.useFixture(Server()) - -``useFixture(fixture)`` calls ``setUp`` on the fixture, schedules a clean up -to clean it up, and schedules a clean up to attach all details_ held by the -fixture to the test case. The fixture object must meet the -``fixtures.Fixture`` protocol (version 0.3.4 or newer, see fixtures_). - -If you have anything beyond the most simple test set up, we recommend that -you put this set up into a ``Fixture`` class. Once there, the fixture can be -easily re-used by other tests and can be combined with other fixtures to make -more complex resources. - - -Skipping tests --------------- - -Many reasons exist to skip a test: a dependency might be missing; a test might -be too expensive and thus should not berun while on battery power; or perhaps -the test is testing an incomplete feature. - -``TestCase.skipTest`` is a simple way to have a test stop running and be -reported as a skipped test, rather than a success, error or failure. For -example:: - - def test_make_symlink(self): - symlink = getattr(os, 'symlink', None) - if symlink is None: - self.skipTest("No symlink support") - symlink(whatever, something_else) - -Using ``skipTest`` means that you can make decisions about what tests to run -as late as possible, and close to the actual tests. Without it, you might be -forced to use convoluted logic during test loading, which is a bit of a mess. - - -Legacy skip support -~~~~~~~~~~~~~~~~~~~ - -If you are using this feature when running your test suite with a legacy -``TestResult`` object that is missing the ``addSkip`` method, then the -``addError`` method will be invoked instead. If you are using a test result -from testtools, you do not have to worry about this. - -In older versions of testtools, ``skipTest`` was known as ``skip``. Since -Python 2.7 added ``skipTest`` support, the ``skip`` name is now deprecated. -No warning is emitted yet – some time in the future we may do so. - - -addOnException --------------- - -Sometimes, you might wish to do something only when a test fails. Perhaps you -need to run expensive diagnostic routines or some such. -``TestCase.addOnException`` allows you to easily do just this. For example:: - - class SomeTest(TestCase): - def setUp(self): - super(SomeTest, self).setUp() - self.server = self.useFixture(SomeServer()) - self.addOnException(self.attach_server_diagnostics) - - def attach_server_diagnostics(self, exc_info): - self.server.prep_for_diagnostics() # Expensive! - self.addDetail('server-diagnostics', self.server.get_diagnostics) - - def test_a_thing(self): - self.assertEqual('cheese', 'chalk') - -In this example, ``attach_server_diagnostics`` will only be called when a test -fails. It is given the exc_info tuple of the error raised by the test, just -in case it is needed. - - -Twisted support ---------------- - -testtools provides *highly experimental* support for running Twisted tests – -tests that return a Deferred_ and rely on the Twisted reactor. You should not -use this feature right now. We reserve the right to change the API and -behaviour without telling you first. - -However, if you are going to, here's how you do it:: - - from testtools import TestCase - from testtools.deferredruntest import AsynchronousDeferredRunTest - - class MyTwistedTests(TestCase): - - run_tests_with = AsynchronousDeferredRunTest - - def test_foo(self): - # ... - return d - -In particular, note that you do *not* have to use a special base ``TestCase`` -in order to run Twisted tests. - -You can also run individual tests within a test case class using the Twisted -test runner:: - - class MyTestsSomeOfWhichAreTwisted(TestCase): - - def test_normal(self): - pass - - @run_test_with(AsynchronousDeferredRunTest) - def test_twisted(self): - # ... - return d - -Here are some tips for converting your Trial tests into testtools tests. - -* Use the ``AsynchronousDeferredRunTest`` runner -* Make sure to upcall to ``setUp`` and ``tearDown`` -* Don't use ``setUpClass`` or ``tearDownClass`` -* Don't expect setting .todo, .timeout or .skip attributes to do anything -* ``flushLoggedErrors`` is ``testtools.deferredruntest.flush_logged_errors`` -* ``assertFailure`` is ``testtools.deferredruntest.assert_fails_with`` -* Trial spins the reactor a couple of times before cleaning it up, - ``AsynchronousDeferredRunTest`` does not. If you rely on this behavior, use - ``AsynchronousDeferredRunTestForBrokenTwisted``. - - -Test helpers -============ - -testtools comes with a few little things that make it a little bit easier to -write tests. - - -TestCase.patch --------------- - -``patch`` is a convenient way to monkey-patch a Python object for the duration -of your test. It's especially useful for testing legacy code. e.g.:: - - def test_foo(self): - my_stream = StringIO() - self.patch(sys, 'stderr', my_stream) - run_some_code_that_prints_to_stderr() - self.assertEqual('', my_stream.getvalue()) - -The call to ``patch`` above masks ``sys.stderr`` with ``my_stream`` so that -anything printed to stderr will be captured in a StringIO variable that can be -actually tested. Once the test is done, the real ``sys.stderr`` is restored to -its rightful place. - - -Creation methods ----------------- - -Often when writing unit tests, you want to create an object that is a -completely normal instance of its type. You don't want there to be anything -special about its properties, because you are testing generic behaviour rather -than specific conditions. - -A lot of the time, test authors do this by making up silly strings and numbers -and passing them to constructors (e.g. 42, 'foo', "bar" etc), and that's -fine. However, sometimes it's useful to be able to create arbitrary objects -at will, without having to make up silly sample data. - -To help with this, ``testtools.TestCase`` implements creation methods called -``getUniqueString`` and ``getUniqueInteger``. They return strings and -integers that are unique within the context of the test that can be used to -assemble more complex objects. Here's a basic example where -``getUniqueString`` is used instead of saying "foo" or "bar" or whatever:: - - class SomeTest(TestCase): - - def test_full_name(self): - first_name = self.getUniqueString() - last_name = self.getUniqueString() - p = Person(first_name, last_name) - self.assertEqual(p.full_name, "%s %s" % (first_name, last_name)) - - -And here's how it could be used to make a complicated test:: - - class TestCoupleLogic(TestCase): - - def make_arbitrary_person(self): - return Person(self.getUniqueString(), self.getUniqueString()) - - def test_get_invitation(self): - a = self.make_arbitrary_person() - b = self.make_arbitrary_person() - couple = Couple(a, b) - event_name = self.getUniqueString() - invitation = couple.get_invitation(event_name) - self.assertEqual( - invitation, - "We invite %s and %s to %s" % ( - a.full_name, b.full_name, event_name)) - -Essentially, creation methods like these are a way of reducing the number of -assumptions in your tests and communicating to test readers that the exact -details of certain variables don't actually matter. - -See pages 419-423 of `xUnit Test Patterns`_ by Gerard Meszaros for a detailed -discussion of creation methods. - - -General helpers -=============== - -Conditional imports -------------------- - -Lots of the time we would like to conditionally import modules. testtools -needs to do this itself, and graciously extends the ability to its users. - -Instead of:: - - try: - from twisted.internet import defer - except ImportError: - defer = None - -You can do:: - - defer = try_import('twisted.internet.defer') - - -Instead of:: - - try: - from StringIO import StringIO - except ImportError: - from io import StringIO - -You can do:: - - StringIO = try_imports(['StringIO.StringIO', 'io.StringIO']) - - -Safe attribute testing ----------------------- - -``hasattr`` is broken_ on many versions of Python. testtools provides -``safe_hasattr``, which can be used to safely test whether an object has a -particular attribute. - - -Nullary callables ------------------ - -Sometimes you want to be able to pass around a function with the arguments -already specified. The normal way of doing this in Python is:: - - nullary = lambda: f(*args, **kwargs) - nullary() - -Which is mostly good enough, but loses a bit of debugging information. If you -take the ``repr()`` of ``nullary``, you're only told that it's a lambda, and -you get none of the juicy meaning that you'd get from the ``repr()`` of ``f``. - -The solution is to use ``Nullary`` instead:: - - nullary = Nullary(f, *args, **kwargs) - nullary() - -Here, ``repr(nullary)`` will be the same as ``repr(f)``. - - -.. _testrepository: https://launchpad.net/testrepository -.. _Trial: http://twistedmatrix.com/documents/current/core/howto/testing.html -.. _nose: http://somethingaboutorange.com/mrl/projects/nose/ -.. _unittest2: http://pypi.python.org/pypi/unittest2 -.. _zope.testrunner: http://pypi.python.org/pypi/zope.testrunner/ -.. _xUnit test patterns: http://xunitpatterns.com/ -.. _fixtures: http://pypi.python.org/pypi/fixtures -.. _unittest: http://docs.python.org/library/unittest.html -.. _doctest: http://docs.python.org/library/doctest.html -.. _Deferred: http://twistedmatrix.com/documents/current/core/howto/defer.html -.. _discover: http://pypi.python.org/pypi/discover -.. _`testtools API docs`: http://mumak.net/testtools/apidocs/ -.. _Distutils: http://docs.python.org/library/distutils.html -.. _`setup configuration`: http://docs.python.org/distutils/configfile.html -.. _broken: http://chipaca.com/post/3210673069/hasattr-17-less-harmful diff --git a/lib/testtools/doc/hacking.rst b/lib/testtools/doc/hacking.rst deleted file mode 100644 index 663eeace3c5..00000000000 --- a/lib/testtools/doc/hacking.rst +++ /dev/null @@ -1,153 +0,0 @@ -========================= -Contributing to testtools -========================= - -Coding style ------------- - -In general, follow `PEP 8`_ except where consistency with the standard -library's unittest_ module would suggest otherwise. - -testtools currently supports Python 2.6 and later, including Python 3. - -Copyright assignment --------------------- - -Part of testtools raison d'etre is to provide Python with improvements to the -testing code it ships. For that reason we require all contributions (that are -non-trivial) to meet one of the following rules: - -* be inapplicable for inclusion in Python. -* be able to be included in Python without further contact with the contributor. -* be copyright assigned to Jonathan M. Lange. - -Please pick one of these and specify it when contributing code to testtools. - - -Licensing ---------- - -All code that is not copyright assigned to Jonathan M. Lange (see Copyright -Assignment above) needs to be licensed under the `MIT license`_ that testtools -uses, so that testtools can ship it. - - -Testing -------- - -Please write tests for every feature. This project ought to be a model -example of well-tested Python code! - -Take particular care to make sure the *intent* of each test is clear. - -You can run tests with ``make check``. - -By default, testtools hides many levels of its own stack when running tests. -This is for the convenience of users, who do not care about how, say, assert -methods are implemented. However, when writing tests for testtools itself, it -is often useful to see all levels of the stack. To do this, add -``run_tests_with = FullStackRunTest`` to the top of a test's class definition. - - -Documentation -------------- - -Documents are written using the Sphinx_ variant of reStructuredText_. All -public methods, functions, classes and modules must have API documentation. -When changing code, be sure to check the API documentation to see if it could -be improved. Before submitting changes to trunk, look over them and see if -the manuals ought to be updated. - - -Source layout -------------- - -The top-level directory contains the ``testtools/`` package directory, and -miscellaneous files like ``README`` and ``setup.py``. - -The ``testtools/`` directory is the Python package itself. It is separated -into submodules for internal clarity, but all public APIs should be “promoted” -into the top-level package by importing them in ``testtools/__init__.py``. -Users of testtools should never import a submodule in order to use a stable -API. Unstable APIs like ``testtools.matchers`` and -``testtools.deferredruntest`` should be exported as submodules. - -Tests belong in ``testtools/tests/``. - - -Committing to trunk -------------------- - -Testtools is maintained using bzr, with its trunk at lp:testtools. This gives -every contributor the ability to commit their work to their own branches. -However permission must be granted to allow contributors to commit to the trunk -branch. - -Commit access to trunk is obtained by joining the testtools-committers -Launchpad team. Membership in this team is contingent on obeying the testtools -contribution policy, see `Copyright Assignment`_ above. - - -Code Review ------------ - -All code must be reviewed before landing on trunk. The process is to create a -branch in launchpad, and submit it for merging to lp:testtools. It will then -be reviewed before it can be merged to trunk. It will be reviewed by someone: - -* not the author -* a committer (member of the `~testtools-committers`_ team) - -As a special exception, while the testtools committers team is small and prone -to blocking, a merge request from a committer that has not been reviewed after -24 hours may be merged by that committer. When the team is larger this policy -will be revisited. - -Code reviewers should look for the quality of what is being submitted, -including conformance with this HACKING file. - -Changes which all users should be made aware of should be documented in NEWS. - - -NEWS management ---------------- - -The file NEWS is structured as a sorted list of releases. Each release can have -a free form description and more or more sections with bullet point items. -Sections in use today are 'Improvements' and 'Changes'. To ease merging between -branches, the bullet points are kept alphabetically sorted. The release NEXT is -permanently present at the top of the list. - - -Release tasks -------------- - -#. Choose a version number, say X.Y.Z -#. Branch from trunk to testtools-X.Y.Z -#. In testtools-X.Y.Z, ensure __init__ has version ``(X, Y, Z, 'final', 0)`` -#. Replace NEXT in NEWS with the version number X.Y.Z, adjusting the reST. -#. Possibly write a blurb into NEWS. -#. Replace any additional references to NEXT with the version being - released. (There should be none other than the ones in these release tasks - which should not be replaced). -#. Commit the changes. -#. Tag the release, bzr tag testtools-X.Y.Z -#. Run 'make release', this: - #. Creates a source distribution and uploads to PyPI - #. Ensures all Fix Committed bugs are in the release milestone - #. Makes a release on Launchpad and uploads the tarball - #. Marks all the Fix Committed bugs as Fix Released - #. Creates a new milestone -#. Merge the release branch testtools-X.Y.Z into trunk. Before the commit, - add a NEXT heading to the top of NEWS and bump the version in __init__.py - e.g. to ``(X, Y, Z+1, 'dev', 0)``. -#. Push trunk to Launchpad -#. If a new series has been created (e.g. 0.10.0), make the series on Launchpad. - -.. _PEP 8: http://www.python.org/dev/peps/pep-0008/ -.. _unittest: http://docs.python.org/library/unittest.html -.. _~testtools-committers: https://launchpad.net/~testtools-committers -.. _MIT license: http://www.opensource.org/licenses/mit-license.php -.. _Sphinx: http://sphinx.pocoo.org/ -.. _restructuredtext: http://docutils.sourceforge.net/rst.html - diff --git a/lib/testtools/doc/index.rst b/lib/testtools/doc/index.rst deleted file mode 100644 index bac47e43794..00000000000 --- a/lib/testtools/doc/index.rst +++ /dev/null @@ -1,36 +0,0 @@ -.. testtools documentation master file, created by - sphinx-quickstart on Sun Nov 28 13:45:40 2010. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -testtools: tasteful testing for Python -====================================== - -testtools is a set of extensions to the Python standard library's unit testing -framework. These extensions have been derived from many years of experience -with unit testing in Python and come from many different sources. testtools -also ports recent unittest changes all the way back to Python 2.4. The next -release of testtools will change that to support versions that are maintained -by the Python community instead, to allow the use of modern language features -within testtools. - - -Contents: - -.. toctree:: - :maxdepth: 1 - - overview - for-test-authors - for-framework-folk - hacking - Changes to testtools - API reference documentation - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/lib/testtools/doc/make.bat b/lib/testtools/doc/make.bat deleted file mode 100644 index f8c1fd520ab..00000000000 --- a/lib/testtools/doc/make.bat +++ /dev/null @@ -1,113 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -set SPHINXBUILD=sphinx-build -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\testtools.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\testtools.ghc - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -:end diff --git a/lib/testtools/doc/overview.rst b/lib/testtools/doc/overview.rst deleted file mode 100644 index cb72893c8bc..00000000000 --- a/lib/testtools/doc/overview.rst +++ /dev/null @@ -1,102 +0,0 @@ -====================================== -testtools: tasteful testing for Python -====================================== - -testtools is a set of extensions to the Python standard library's unit testing -framework. These extensions have been derived from many years of experience -with unit testing in Python and come from many different sources. testtools -supports Python versions all the way back to Python 2.4. The next release of -testtools will change that to support versions that are maintained by the -Python community instead, to allow the use of modern language features within -testtools. - -What better way to start than with a contrived code snippet?:: - - from testtools import TestCase - from testtools.content import Content - from testtools.content_type import UTF8_TEXT - from testtools.matchers import Equals - - from myproject import SillySquareServer - - class TestSillySquareServer(TestCase): - - def setUp(self): - super(TestSillySquare, self).setUp() - self.server = self.useFixture(SillySquareServer()) - self.addCleanup(self.attach_log_file) - - def attach_log_file(self): - self.addDetail( - 'log-file', - Content(UTF8_TEXT - lambda: open(self.server.logfile, 'r').readlines())) - - def test_server_is_cool(self): - self.assertThat(self.server.temperature, Equals("cool")) - - def test_square(self): - self.assertThat(self.server.silly_square_of(7), Equals(49)) - - -Why use testtools? -================== - -Better assertion methods ------------------------- - -The standard assertion methods that come with unittest aren't as helpful as -they could be, and there aren't quite enough of them. testtools adds -``assertIn``, ``assertIs``, ``assertIsInstance`` and their negatives. - - -Matchers: better than assertion methods ---------------------------------------- - -Of course, in any serious project you want to be able to have assertions that -are specific to that project and the particular problem that it is addressing. -Rather than forcing you to define your own assertion methods and maintain your -own inheritance hierarchy of ``TestCase`` classes, testtools lets you write -your own "matchers", custom predicates that can be plugged into a unit test:: - - def test_response_has_bold(self): - # The response has bold text. - response = self.server.getResponse() - self.assertThat(response, HTMLContains(Tag('bold', 'b'))) - - -More debugging info, when you need it --------------------------------------- - -testtools makes it easy to add arbitrary data to your test result. If you -want to know what's in a log file when a test fails, or what the load was on -the computer when a test started, or what files were open, you can add that -information with ``TestCase.addDetail``, and it will appear in the test -results if that test fails. - - -Extend unittest, but stay compatible and re-usable --------------------------------------------------- - -testtools goes to great lengths to allow serious test authors and test -*framework* authors to do whatever they like with their tests and their -extensions while staying compatible with the standard library's unittest. - -testtools has completely parametrized how exceptions raised in tests are -mapped to ``TestResult`` methods and how tests are actually executed (ever -wanted ``tearDown`` to be called regardless of whether ``setUp`` succeeds?) - -It also provides many simple but handy utilities, like the ability to clone a -test, a ``MultiTestResult`` object that lets many result objects get the -results from one test suite, adapters to bring legacy ``TestResult`` objects -into our new golden age. - - -Cross-Python compatibility --------------------------- - -testtools gives you the very latest in unit testing technology in a way that -will work with Python 2.6, 2.7 and 3.1. - -If you wish to use testtools with Python 2.4 or 2.5, then please use testtools -0.9.15. diff --git a/lib/testtools/scripts/README b/lib/testtools/scripts/README deleted file mode 100644 index 648f1057059..00000000000 --- a/lib/testtools/scripts/README +++ /dev/null @@ -1,3 +0,0 @@ -These are scripts to help with building, maintaining and releasing testtools. - -There is little here for anyone except a testtools contributor. diff --git a/lib/testtools/scripts/_lp_release.py b/lib/testtools/scripts/_lp_release.py deleted file mode 100644 index 20afd0199ed..00000000000 --- a/lib/testtools/scripts/_lp_release.py +++ /dev/null @@ -1,230 +0,0 @@ -#!/usr/bin/python - -"""Release testtools on Launchpad. - -Steps: - 1. Make sure all "Fix committed" bugs are assigned to 'next' - 2. Rename 'next' to the new version - 3. Release the milestone - 4. Upload the tarball - 5. Create a new 'next' milestone - 6. Mark all "Fix committed" bugs in the milestone as "Fix released" - -Assumes that NEWS is in the parent directory, that the release sections are -underlined with '~' and the subsections are underlined with '-'. - -Assumes that this file is in the 'scripts' directory a testtools tree that has -already had a tarball built and uploaded with 'python setup.py sdist upload ---sign'. -""" - -from datetime import datetime, timedelta, tzinfo -import logging -import os -import sys - -from launchpadlib.launchpad import Launchpad -from launchpadlib import uris - - -APP_NAME = 'testtools-lp-release' -CACHE_DIR = os.path.expanduser('~/.launchpadlib/cache') -SERVICE_ROOT = uris.LPNET_SERVICE_ROOT - -FIX_COMMITTED = u"Fix Committed" -FIX_RELEASED = u"Fix Released" - -# Launchpad file type for a tarball upload. -CODE_RELEASE_TARBALL = 'Code Release Tarball' - -PROJECT_NAME = 'testtools' -NEXT_MILESTONE_NAME = 'next' - - -class _UTC(tzinfo): - """UTC""" - - def utcoffset(self, dt): - return timedelta(0) - - def tzname(self, dt): - return "UTC" - - def dst(self, dt): - return timedelta(0) - -UTC = _UTC() - - -def configure_logging(): - level = logging.INFO - log = logging.getLogger(APP_NAME) - log.setLevel(level) - handler = logging.StreamHandler() - handler.setLevel(level) - formatter = logging.Formatter("%(levelname)s: %(message)s") - handler.setFormatter(formatter) - log.addHandler(handler) - return log -LOG = configure_logging() - - -def get_path(relpath): - """Get the absolute path for something relative to this file.""" - return os.path.abspath( - os.path.join( - os.path.dirname(os.path.dirname(__file__)), relpath)) - - -def assign_fix_committed_to_next(testtools, next_milestone): - """Find all 'Fix Committed' and make sure they are in 'next'.""" - fixed_bugs = list(testtools.searchTasks(status=FIX_COMMITTED)) - for task in fixed_bugs: - LOG.debug("%s" % (task.title,)) - if task.milestone != next_milestone: - task.milestone = next_milestone - LOG.info("Re-assigning %s" % (task.title,)) - task.lp_save() - - -def rename_milestone(next_milestone, new_name): - """Rename 'next_milestone' to 'new_name'.""" - LOG.info("Renaming %s to %s" % (next_milestone.name, new_name)) - next_milestone.name = new_name - next_milestone.lp_save() - - -def get_release_notes_and_changelog(news_path): - release_notes = [] - changelog = [] - state = None - last_line = None - - def is_heading_marker(line, marker_char): - return line and line == marker_char * len(line) - - LOG.debug("Loading NEWS from %s" % (news_path,)) - with open(news_path, 'r') as news: - for line in news: - line = line.strip() - if state is None: - if is_heading_marker(line, '~'): - milestone_name = last_line - state = 'release-notes' - else: - last_line = line - elif state == 'title': - # The line after the title is a heading marker line, so we - # ignore it and change state. That which follows are the - # release notes. - state = 'release-notes' - elif state == 'release-notes': - if is_heading_marker(line, '-'): - state = 'changelog' - # Last line in the release notes is actually the first - # line of the changelog. - changelog = [release_notes.pop(), line] - else: - release_notes.append(line) - elif state == 'changelog': - if is_heading_marker(line, '~'): - # Last line in changelog is actually the first line of the - # next section. - changelog.pop() - break - else: - changelog.append(line) - else: - raise ValueError("Couldn't parse NEWS") - - release_notes = '\n'.join(release_notes).strip() + '\n' - changelog = '\n'.join(changelog).strip() + '\n' - return milestone_name, release_notes, changelog - - -def release_milestone(milestone, release_notes, changelog): - date_released = datetime.now(tz=UTC) - LOG.info( - "Releasing milestone: %s, date %s" % (milestone.name, date_released)) - release = milestone.createProductRelease( - date_released=date_released, - changelog=changelog, - release_notes=release_notes, - ) - milestone.is_active = False - milestone.lp_save() - return release - - -def create_milestone(series, name): - """Create a new milestone in the same series as 'release_milestone'.""" - LOG.info("Creating milestone %s in series %s" % (name, series.name)) - return series.newMilestone(name=name) - - -def close_fixed_bugs(milestone): - tasks = list(milestone.searchTasks()) - for task in tasks: - LOG.debug("Found %s" % (task.title,)) - if task.status == FIX_COMMITTED: - LOG.info("Closing %s" % (task.title,)) - task.status = FIX_RELEASED - else: - LOG.warning( - "Bug not fixed, removing from milestone: %s" % (task.title,)) - task.milestone = None - task.lp_save() - - -def upload_tarball(release, tarball_path): - with open(tarball_path) as tarball: - tarball_content = tarball.read() - sig_path = tarball_path + '.asc' - with open(sig_path) as sig: - sig_content = sig.read() - tarball_name = os.path.basename(tarball_path) - LOG.info("Uploading tarball: %s" % (tarball_path,)) - release.add_file( - file_type=CODE_RELEASE_TARBALL, - file_content=tarball_content, filename=tarball_name, - signature_content=sig_content, - signature_filename=sig_path, - content_type="application/x-gzip; charset=binary") - - -def release_project(launchpad, project_name, next_milestone_name): - testtools = launchpad.projects[project_name] - next_milestone = testtools.getMilestone(name=next_milestone_name) - release_name, release_notes, changelog = get_release_notes_and_changelog( - get_path('NEWS')) - LOG.info("Releasing %s %s" % (project_name, release_name)) - # Since reversing these operations is hard, and inspecting errors from - # Launchpad is also difficult, do some looking before leaping. - errors = [] - tarball_path = get_path('dist/%s-%s.tar.gz' % (project_name, release_name,)) - if not os.path.isfile(tarball_path): - errors.append("%s does not exist" % (tarball_path,)) - if not os.path.isfile(tarball_path + '.asc'): - errors.append("%s does not exist" % (tarball_path + '.asc',)) - if testtools.getMilestone(name=release_name): - errors.append("Milestone %s exists on %s" % (release_name, project_name)) - if errors: - for error in errors: - LOG.error(error) - return 1 - assign_fix_committed_to_next(testtools, next_milestone) - rename_milestone(next_milestone, release_name) - release = release_milestone(next_milestone, release_notes, changelog) - upload_tarball(release, tarball_path) - create_milestone(next_milestone.series_target, next_milestone_name) - close_fixed_bugs(next_milestone) - return 0 - - -def main(args): - launchpad = Launchpad.login_with(APP_NAME, SERVICE_ROOT, CACHE_DIR) - return release_project(launchpad, PROJECT_NAME, NEXT_MILESTONE_NAME) - - -if __name__ == '__main__': - sys.exit(main(sys.argv)) diff --git a/lib/testtools/scripts/all-pythons b/lib/testtools/scripts/all-pythons deleted file mode 100755 index 10fd6deab3a..00000000000 --- a/lib/testtools/scripts/all-pythons +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/python - -"""Run the testtools test suite for all supported Pythons. - -Prints output as a subunit test suite. If anything goes to stderr, that is -treated as a test error. If a Python is not available, then it is skipped. -""" - -from datetime import datetime -import os -import subprocess -import sys - -import subunit -from subunit import ( - iso8601, - _make_stream_binary, - TestProtocolClient, - TestProtocolServer, - ) -from testtools import ( - PlaceHolder, - TestCase, - ) -from testtools.compat import BytesIO -from testtools.content import text_content - - -ROOT = os.path.dirname(os.path.dirname(__file__)) - - -def run_for_python(version, result, tests): - if not tests: - tests = ['testtools.tests.test_suite'] - # XXX: This could probably be broken up and put into subunit. - python = 'python%s' % (version,) - # XXX: Correct API, but subunit doesn't support it. :( - # result.tags(set(python), set()) - result.time(now()) - test = PlaceHolder(''.join(c for c in python if c != '.')) - process = subprocess.Popen( - '%s -c pass' % (python,), shell=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) - process.communicate() - - if process.returncode: - result.startTest(test) - result.addSkip(test, reason='%s not available' % (python,)) - result.stopTest(test) - return - - env = os.environ.copy() - if env.get('PYTHONPATH', None): - env['PYTHONPATH'] = os.pathsep.join([ROOT, env['PYTHONPATH']]) - else: - env['PYTHONPATH'] = ROOT - result.time(now()) - protocol = TestProtocolServer(result) - subunit_path = os.path.join(os.path.dirname(subunit.__file__), 'run.py') - cmd = [ - python, - '-W', 'ignore:Module testtools was already imported', - subunit_path] - cmd.extend(tests) - process = subprocess.Popen( - cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) - _make_stream_binary(process.stdout) - _make_stream_binary(process.stderr) - # XXX: This buffers everything. Bad for memory, bad for getting progress - # on jenkins. - output, error = process.communicate() - protocol.readFrom(BytesIO(output)) - if error: - result.startTest(test) - result.addError(test, details={ - 'stderr': text_content(error), - }) - result.stopTest(test) - result.time(now()) - # XXX: Correct API, but subunit doesn't support it. :( - #result.tags(set(), set(python)) - - -def now(): - return datetime.utcnow().replace(tzinfo=iso8601.Utc()) - - - -if __name__ == '__main__': - sys.path.append(ROOT) - result = TestProtocolClient(sys.stdout) - for version in '2.6 2.7 3.0 3.1 3.2'.split(): - run_for_python(version, result, sys.argv[1:]) diff --git a/lib/testtools/scripts/update-rtfd b/lib/testtools/scripts/update-rtfd deleted file mode 100755 index 92a19daaa6c..00000000000 --- a/lib/testtools/scripts/update-rtfd +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/python - -from StringIO import StringIO -from urllib2 import urlopen - - -WEB_HOOK = 'http://readthedocs.org/build/588' - - -if __name__ == '__main__': - urlopen(WEB_HOOK, data=' ') diff --git a/lib/testtools/setup.cfg b/lib/testtools/setup.cfg deleted file mode 100644 index 9f95adde2b4..00000000000 --- a/lib/testtools/setup.cfg +++ /dev/null @@ -1,4 +0,0 @@ -[test] -test_module = testtools.tests -buffer=1 -catch=1 diff --git a/lib/testtools/setup.py b/lib/testtools/setup.py deleted file mode 100755 index 7ecd6d24d26..00000000000 --- a/lib/testtools/setup.py +++ /dev/null @@ -1,85 +0,0 @@ -#!/usr/bin/env python -"""Distutils installer for testtools.""" - -from distutils.core import setup -import email -import os - -import testtools - - -def get_revno(): - import bzrlib.errors - import bzrlib.workingtree - try: - t = bzrlib.workingtree.WorkingTree.open_containing(__file__)[0] - except (bzrlib.errors.NotBranchError, bzrlib.errors.NoWorkingTree): - return None - else: - return t.branch.revno() - - -def get_version_from_pkg_info(): - """Get the version from PKG-INFO file if we can.""" - pkg_info_path = os.path.join(os.path.dirname(__file__), 'PKG-INFO') - try: - pkg_info_file = open(pkg_info_path, 'r') - except (IOError, OSError): - return None - try: - pkg_info = email.message_from_file(pkg_info_file) - except email.MessageError: - return None - return pkg_info.get('Version', None) - - -def get_version(): - """Return the version of testtools that we are building.""" - version = '.'.join( - str(component) for component in testtools.__version__[0:3]) - phase = testtools.__version__[3] - if phase == 'final': - return version - pkg_info_version = get_version_from_pkg_info() - if pkg_info_version: - return pkg_info_version - revno = get_revno() - if revno is None: - # Apparently if we just say "snapshot" then distribute won't accept it - # as satisfying versioned dependencies. This is a problem for the - # daily build version. - return "snapshot-%s" % (version,) - if phase == 'alpha': - # No idea what the next version will be - return 'next-r%s' % revno - else: - # Preserve the version number but give it a revno prefix - return version + '-r%s' % revno - - -def get_long_description(): - manual_path = os.path.join( - os.path.dirname(__file__), 'doc/overview.rst') - return open(manual_path).read() - - -setup(name='testtools', - author='Jonathan M. Lange', - author_email='jml+testtools@mumak.net', - url='https://launchpad.net/testtools', - description=('Extensions to the Python standard library unit testing ' - 'framework'), - long_description=get_long_description(), - version=get_version(), - classifiers=["License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3", - ], - packages=[ - 'testtools', - 'testtools.matchers', - 'testtools.testresult', - 'testtools.tests', - 'testtools.tests.matchers', - ], - cmdclass={'test': testtools.TestCommand}, - zip_safe=False) diff --git a/lib/testtools/testtools/__init__.py b/lib/testtools/testtools/__init__.py deleted file mode 100644 index d722ce544a3..00000000000 --- a/lib/testtools/testtools/__init__.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright (c) 2008-2012 testtools developers. See LICENSE for details. - -"""Extensions to the standard Python unittest library.""" - -__all__ = [ - 'clone_test_with_new_id', - 'ConcurrentTestSuite', - 'ErrorHolder', - 'ExpectedException', - 'ExtendedToOriginalDecorator', - 'FixtureSuite', - 'iterate_tests', - 'MultipleExceptions', - 'MultiTestResult', - 'PlaceHolder', - 'run_test_with', - 'Tagger', - 'TestCase', - 'TestCommand', - 'TestByTestResult', - 'TestResult', - 'TestResultDecorator', - 'TextTestResult', - 'RunTest', - 'skip', - 'skipIf', - 'skipUnless', - 'ThreadsafeForwardingResult', - 'try_import', - 'try_imports', - ] - -from testtools.helpers import ( - try_import, - try_imports, - ) -from testtools.matchers._impl import ( - Matcher, - ) -# Shut up, pyflakes. We are importing for documentation, not for namespacing. -Matcher - -from testtools.runtest import ( - MultipleExceptions, - RunTest, - ) -from testtools.testcase import ( - ErrorHolder, - ExpectedException, - PlaceHolder, - TestCase, - clone_test_with_new_id, - run_test_with, - skip, - skipIf, - skipUnless, - ) -from testtools.testresult import ( - ExtendedToOriginalDecorator, - MultiTestResult, - Tagger, - TestByTestResult, - TestResult, - TestResultDecorator, - TextTestResult, - ThreadsafeForwardingResult, - ) -from testtools.testsuite import ( - ConcurrentTestSuite, - FixtureSuite, - iterate_tests, - ) -from testtools.distutilscmd import ( - TestCommand, -) - -# same format as sys.version_info: "A tuple containing the five components of -# the version number: major, minor, micro, releaselevel, and serial. All -# values except releaselevel are integers; the release level is 'alpha', -# 'beta', 'candidate', or 'final'. The version_info value corresponding to the -# Python version 2.0 is (2, 0, 0, 'final', 0)." Additionally we use a -# releaselevel of 'dev' for unreleased under-development code. -# -# If the releaselevel is 'alpha' then the major/minor/micro components are not -# established at this point, and setup.py will use a version of next-$(revno). -# If the releaselevel is 'final', then the tarball will be major.minor.micro. -# Otherwise it is major.minor.micro~$(revno). - -__version__ = (0, 9, 25, 'dev', 0) diff --git a/lib/testtools/testtools/_compat2x.py b/lib/testtools/testtools/_compat2x.py deleted file mode 100644 index 2b25c13e081..00000000000 --- a/lib/testtools/testtools/_compat2x.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) 2011 testtools developers. See LICENSE for details. - -"""Compatibility helpers that are valid syntax in Python 2.x. - -Only add things here if they *only* work in Python 2.x or are Python 2 -alternatives to things that *only* work in Python 3.x. -""" - -__all__ = [ - 'reraise', - ] - - -def reraise(exc_class, exc_obj, exc_tb, _marker=object()): - """Re-raise an exception received from sys.exc_info() or similar.""" - raise exc_class, exc_obj, exc_tb - diff --git a/lib/testtools/testtools/_compat3x.py b/lib/testtools/testtools/_compat3x.py deleted file mode 100644 index 7a482c14b43..00000000000 --- a/lib/testtools/testtools/_compat3x.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) 2011 testtools developers. See LICENSE for details. - -"""Compatibility helpers that are valid syntax in Python 3.x. - -Only add things here if they *only* work in Python 3.x or are Python 3 -alternatives to things that *only* work in Python 2.x. -""" - -__all__ = [ - 'reraise', - ] - - -def reraise(exc_class, exc_obj, exc_tb, _marker=object()): - """Re-raise an exception received from sys.exc_info() or similar.""" - raise exc_obj.with_traceback(exc_tb) - diff --git a/lib/testtools/testtools/_spinner.py b/lib/testtools/testtools/_spinner.py deleted file mode 100644 index baf455a5f94..00000000000 --- a/lib/testtools/testtools/_spinner.py +++ /dev/null @@ -1,316 +0,0 @@ -# Copyright (c) 2010 testtools developers. See LICENSE for details. - -"""Evil reactor-spinning logic for running Twisted tests. - -This code is highly experimental, liable to change and not to be trusted. If -you couldn't write this yourself, you should not be using it. -""" - -__all__ = [ - 'DeferredNotFired', - 'extract_result', - 'NoResultError', - 'not_reentrant', - 'ReentryError', - 'Spinner', - 'StaleJunkError', - 'TimeoutError', - 'trap_unhandled_errors', - ] - -import signal - -from testtools.monkey import MonkeyPatcher - -from twisted.internet import defer -from twisted.internet.base import DelayedCall -from twisted.internet.interfaces import IReactorThreads -from twisted.python.failure import Failure -from twisted.python.util import mergeFunctionMetadata - - -class ReentryError(Exception): - """Raised when we try to re-enter a function that forbids it.""" - - def __init__(self, function): - Exception.__init__(self, - "%r in not re-entrant but was called within a call to itself." - % (function,)) - - -def not_reentrant(function, _calls={}): - """Decorates a function as not being re-entrant. - - The decorated function will raise an error if called from within itself. - """ - def decorated(*args, **kwargs): - if _calls.get(function, False): - raise ReentryError(function) - _calls[function] = True - try: - return function(*args, **kwargs) - finally: - _calls[function] = False - return mergeFunctionMetadata(function, decorated) - - -class DeferredNotFired(Exception): - """Raised when we extract a result from a Deferred that's not fired yet.""" - - -def extract_result(deferred): - """Extract the result from a fired deferred. - - It can happen that you have an API that returns Deferreds for - compatibility with Twisted code, but is in fact synchronous, i.e. the - Deferreds it returns have always fired by the time it returns. In this - case, you can use this function to convert the result back into the usual - form for a synchronous API, i.e. the result itself or a raised exception. - - It would be very bad form to use this as some way of checking if a - Deferred has fired. - """ - failures = [] - successes = [] - deferred.addCallbacks(successes.append, failures.append) - if len(failures) == 1: - failures[0].raiseException() - elif len(successes) == 1: - return successes[0] - else: - raise DeferredNotFired("%r has not fired yet." % (deferred,)) - - -def trap_unhandled_errors(function, *args, **kwargs): - """Run a function, trapping any unhandled errors in Deferreds. - - Assumes that 'function' will have handled any errors in Deferreds by the - time it is complete. This is almost never true of any Twisted code, since - you can never tell when someone has added an errback to a Deferred. - - If 'function' raises, then don't bother doing any unhandled error - jiggery-pokery, since something horrible has probably happened anyway. - - :return: A tuple of '(result, error)', where 'result' is the value - returned by 'function' and 'error' is a list of 'defer.DebugInfo' - objects that have unhandled errors in Deferreds. - """ - real_DebugInfo = defer.DebugInfo - debug_infos = [] - def DebugInfo(): - info = real_DebugInfo() - debug_infos.append(info) - return info - defer.DebugInfo = DebugInfo - try: - result = function(*args, **kwargs) - finally: - defer.DebugInfo = real_DebugInfo - errors = [] - for info in debug_infos: - if info.failResult is not None: - errors.append(info) - # Disable the destructor that logs to error. We are already - # catching the error here. - info.__del__ = lambda: None - return result, errors - - -class TimeoutError(Exception): - """Raised when run_in_reactor takes too long to run a function.""" - - def __init__(self, function, timeout): - Exception.__init__(self, - "%r took longer than %s seconds" % (function, timeout)) - - -class NoResultError(Exception): - """Raised when the reactor has stopped but we don't have any result.""" - - def __init__(self): - Exception.__init__(self, - "Tried to get test's result from Deferred when no result is " - "available. Probably means we received SIGINT or similar.") - - -class StaleJunkError(Exception): - """Raised when there's junk in the spinner from a previous run.""" - - def __init__(self, junk): - Exception.__init__(self, - "There was junk in the spinner from a previous run. " - "Use clear_junk() to clear it out: %r" % (junk,)) - - -class Spinner(object): - """Spin the reactor until a function is done. - - This class emulates the behaviour of twisted.trial in that it grotesquely - and horribly spins the Twisted reactor while a function is running, and - then kills the reactor when that function is complete and all the - callbacks in its chains are done. - """ - - _UNSET = object() - - # Signals that we save and restore for each spin. - _PRESERVED_SIGNALS = [ - 'SIGINT', - 'SIGTERM', - 'SIGCHLD', - ] - - # There are many APIs within Twisted itself where a Deferred fires but - # leaves cleanup work scheduled for the reactor to do. Arguably, many of - # these are bugs. As such, we provide a facility to iterate the reactor - # event loop a number of times after every call, in order to shake out - # these buggy-but-commonplace events. The default is 0, because that is - # the ideal, and it actually works for many cases. - _OBLIGATORY_REACTOR_ITERATIONS = 0 - - def __init__(self, reactor, debug=False): - """Construct a Spinner. - - :param reactor: A Twisted reactor. - :param debug: Whether or not to enable Twisted's debugging. Defaults - to False. - """ - self._reactor = reactor - self._timeout_call = None - self._success = self._UNSET - self._failure = self._UNSET - self._saved_signals = [] - self._junk = [] - self._debug = debug - - def _cancel_timeout(self): - if self._timeout_call: - self._timeout_call.cancel() - - def _get_result(self): - if self._failure is not self._UNSET: - self._failure.raiseException() - if self._success is not self._UNSET: - return self._success - raise NoResultError() - - def _got_failure(self, result): - self._cancel_timeout() - self._failure = result - - def _got_success(self, result): - self._cancel_timeout() - self._success = result - - def _stop_reactor(self, ignored=None): - """Stop the reactor!""" - self._reactor.crash() - - def _timed_out(self, function, timeout): - e = TimeoutError(function, timeout) - self._failure = Failure(e) - self._stop_reactor() - - def _clean(self): - """Clean up any junk in the reactor. - - Will always iterate the reactor a number of times equal to - ``Spinner._OBLIGATORY_REACTOR_ITERATIONS``. This is to work around - bugs in various Twisted APIs where a Deferred fires but still leaves - work (e.g. cancelling a call, actually closing a connection) for the - reactor to do. - """ - for i in range(self._OBLIGATORY_REACTOR_ITERATIONS): - self._reactor.iterate(0) - junk = [] - for delayed_call in self._reactor.getDelayedCalls(): - delayed_call.cancel() - junk.append(delayed_call) - for selectable in self._reactor.removeAll(): - # Twisted sends a 'KILL' signal to selectables that provide - # IProcessTransport. Since only _dumbwin32proc processes do this, - # we aren't going to bother. - junk.append(selectable) - if IReactorThreads.providedBy(self._reactor): - if self._reactor.threadpool is not None: - self._reactor._stopThreadPool() - self._junk.extend(junk) - return junk - - def clear_junk(self): - """Clear out our recorded junk. - - :return: Whatever junk was there before. - """ - junk = self._junk - self._junk = [] - return junk - - def get_junk(self): - """Return any junk that has been found on the reactor.""" - return self._junk - - def _save_signals(self): - available_signals = [ - getattr(signal, name, None) for name in self._PRESERVED_SIGNALS] - self._saved_signals = [ - (sig, signal.getsignal(sig)) for sig in available_signals if sig] - - def _restore_signals(self): - for sig, hdlr in self._saved_signals: - signal.signal(sig, hdlr) - self._saved_signals = [] - - @not_reentrant - def run(self, timeout, function, *args, **kwargs): - """Run 'function' in a reactor. - - If 'function' returns a Deferred, the reactor will keep spinning until - the Deferred fires and its chain completes or until the timeout is - reached -- whichever comes first. - - :raise TimeoutError: If 'timeout' is reached before the Deferred - returned by 'function' has completed its callback chain. - :raise NoResultError: If the reactor is somehow interrupted before - the Deferred returned by 'function' has completed its callback - chain. - :raise StaleJunkError: If there's junk in the spinner from a previous - run. - :return: Whatever is at the end of the function's callback chain. If - it's an error, then raise that. - """ - debug = MonkeyPatcher() - if self._debug: - debug.add_patch(defer.Deferred, 'debug', True) - debug.add_patch(DelayedCall, 'debug', True) - debug.patch() - try: - junk = self.get_junk() - if junk: - raise StaleJunkError(junk) - self._save_signals() - self._timeout_call = self._reactor.callLater( - timeout, self._timed_out, function, timeout) - # Calling 'stop' on the reactor will make it impossible to - # re-start the reactor. Since the default signal handlers for - # TERM, BREAK and INT all call reactor.stop(), we'll patch it over - # with crash. XXX: It might be a better idea to either install - # custom signal handlers or to override the methods that are - # Twisted's signal handlers. - stop, self._reactor.stop = self._reactor.stop, self._reactor.crash - def run_function(): - d = defer.maybeDeferred(function, *args, **kwargs) - d.addCallbacks(self._got_success, self._got_failure) - d.addBoth(self._stop_reactor) - try: - self._reactor.callWhenRunning(run_function) - self._reactor.run() - finally: - self._reactor.stop = stop - self._restore_signals() - try: - return self._get_result() - finally: - self._clean() - finally: - debug.restore() diff --git a/lib/testtools/testtools/compat.py b/lib/testtools/testtools/compat.py deleted file mode 100644 index 375eca2c02a..00000000000 --- a/lib/testtools/testtools/compat.py +++ /dev/null @@ -1,393 +0,0 @@ -# Copyright (c) 2008-2011 testtools developers. See LICENSE for details. - -"""Compatibility support for python 2 and 3.""" - -__metaclass__ = type -__all__ = [ - '_b', - '_u', - 'advance_iterator', - 'all', - 'BytesIO', - 'classtypes', - 'isbaseexception', - 'istext', - 'str_is_unicode', - 'StringIO', - 'reraise', - 'unicode_output_stream', - ] - -import codecs -import linecache -import locale -import os -import re -import sys -import traceback -import unicodedata - -from testtools.helpers import try_imports - -BytesIO = try_imports(['StringIO.StringIO', 'io.BytesIO']) -StringIO = try_imports(['StringIO.StringIO', 'io.StringIO']) - -try: - from testtools import _compat2x as _compat -except SyntaxError: - from testtools import _compat3x as _compat - -reraise = _compat.reraise - - -__u_doc = """A function version of the 'u' prefix. - -This is needed becayse the u prefix is not usable in Python 3 but is required -in Python 2 to get a unicode object. - -To migrate code that was written as u'\u1234' in Python 2 to 2+3 change -it to be _u('\u1234'). The Python 3 interpreter will decode it -appropriately and the no-op _u for Python 3 lets it through, in Python -2 we then call unicode-escape in the _u function. -""" - -if sys.version_info > (3, 0): - import builtins - def _u(s): - return s - _r = ascii - def _b(s): - """A byte literal.""" - return s.encode("latin-1") - advance_iterator = next - # GZ 2011-08-24: Seems istext() is easy to misuse and makes for bad code. - def istext(x): - return isinstance(x, str) - def classtypes(): - return (type,) - str_is_unicode = True -else: - import __builtin__ as builtins - def _u(s): - # The double replace mangling going on prepares the string for - # unicode-escape - \foo is preserved, \u and \U are decoded. - return (s.replace("\\", "\\\\").replace("\\\\u", "\\u") - .replace("\\\\U", "\\U").decode("unicode-escape")) - _r = repr - def _b(s): - return s - advance_iterator = lambda it: it.next() - def istext(x): - return isinstance(x, basestring) - def classtypes(): - import types - return (type, types.ClassType) - str_is_unicode = sys.platform == "cli" - -_u.__doc__ = __u_doc - - -if sys.version_info > (2, 5): - all = all - _error_repr = BaseException.__repr__ - def isbaseexception(exception): - """Return whether exception inherits from BaseException only""" - return (isinstance(exception, BaseException) - and not isinstance(exception, Exception)) -else: - def all(iterable): - """If contents of iterable all evaluate as boolean True""" - for obj in iterable: - if not obj: - return False - return True - def _error_repr(exception): - """Format an exception instance as Python 2.5 and later do""" - return exception.__class__.__name__ + repr(exception.args) - def isbaseexception(exception): - """Return whether exception would inherit from BaseException only - - This approximates the hierarchy in Python 2.5 and later, compare the - difference between the diagrams at the bottom of the pages: - - - """ - return isinstance(exception, (KeyboardInterrupt, SystemExit)) - - -# GZ 2011-08-24: Using isinstance checks like this encourages bad interfaces, -# there should be better ways to write code needing this. -if not issubclass(getattr(builtins, "bytes", str), str): - def _isbytes(x): - return isinstance(x, bytes) -else: - # Never return True on Pythons that provide the name but not the real type - def _isbytes(x): - return False - - -def _slow_escape(text): - """Escape unicode ``text`` leaving printable characters unmodified - - The behaviour emulates the Python 3 implementation of repr, see - unicode_repr in unicodeobject.c and isprintable definition. - - Because this iterates over the input a codepoint at a time, it's slow, and - does not handle astral characters correctly on Python builds with 16 bit - rather than 32 bit unicode type. - """ - output = [] - for c in text: - o = ord(c) - if o < 256: - if o < 32 or 126 < o < 161: - output.append(c.encode("unicode-escape")) - elif o == 92: - # Separate due to bug in unicode-escape codec in Python 2.4 - output.append("\\\\") - else: - output.append(c) - else: - # To get correct behaviour would need to pair up surrogates here - if unicodedata.category(c)[0] in "CZ": - output.append(c.encode("unicode-escape")) - else: - output.append(c) - return "".join(output) - - -def text_repr(text, multiline=None): - """Rich repr for ``text`` returning unicode, triple quoted if ``multiline``. - """ - is_py3k = sys.version_info > (3, 0) - nl = _isbytes(text) and bytes((0xA,)) or "\n" - if multiline is None: - multiline = nl in text - if not multiline and (is_py3k or not str_is_unicode and type(text) is str): - # Use normal repr for single line of unicode on Python 3 or bytes - return repr(text) - prefix = repr(text[:0])[:-2] - if multiline: - # To escape multiline strings, split and process each line in turn, - # making sure that quotes are not escaped. - if is_py3k: - offset = len(prefix) + 1 - lines = [] - for l in text.split(nl): - r = repr(l) - q = r[-1] - lines.append(r[offset:-1].replace("\\" + q, q)) - elif not str_is_unicode and isinstance(text, str): - lines = [l.encode("string-escape").replace("\\'", "'") - for l in text.split("\n")] - else: - lines = [_slow_escape(l) for l in text.split("\n")] - # Combine the escaped lines and append two of the closing quotes, - # then iterate over the result to escape triple quotes correctly. - _semi_done = "\n".join(lines) + "''" - p = 0 - while True: - p = _semi_done.find("'''", p) - if p == -1: - break - _semi_done = "\\".join([_semi_done[:p], _semi_done[p:]]) - p += 2 - return "".join([prefix, "'''\\\n", _semi_done, "'"]) - escaped_text = _slow_escape(text) - # Determine which quote character to use and if one gets prefixed with a - # backslash following the same logic Python uses for repr() on strings - quote = "'" - if "'" in text: - if '"' in text: - escaped_text = escaped_text.replace("'", "\\'") - else: - quote = '"' - return "".join([prefix, quote, escaped_text, quote]) - - -def unicode_output_stream(stream): - """Get wrapper for given stream that writes any unicode without exception - - Characters that can't be coerced to the encoding of the stream, or 'ascii' - if valid encoding is not found, will be replaced. The original stream may - be returned in situations where a wrapper is determined unneeded. - - The wrapper only allows unicode to be written, not non-ascii bytestrings, - which is a good thing to ensure sanity and sanitation. - """ - if sys.platform == "cli": - # Best to never encode before writing in IronPython - return stream - try: - writer = codecs.getwriter(stream.encoding or "") - except (AttributeError, LookupError): - # GZ 2010-06-16: Python 3 StringIO ends up here, but probably needs - # different handling as it doesn't want bytestrings - return codecs.getwriter("ascii")(stream, "replace") - if writer.__module__.rsplit(".", 1)[1].startswith("utf"): - # The current stream has a unicode encoding so no error handler is needed - if sys.version_info > (3, 0): - return stream - return writer(stream) - if sys.version_info > (3, 0): - # Python 3 doesn't seem to make this easy, handle a common case - try: - return stream.__class__(stream.buffer, stream.encoding, "replace", - stream.newlines, stream.line_buffering) - except AttributeError: - pass - return writer(stream, "replace") - - -# The default source encoding is actually "iso-8859-1" until Python 2.5 but -# using non-ascii causes a deprecation warning in 2.4 and it's cleaner to -# treat all versions the same way -_default_source_encoding = "ascii" - -# Pattern specified in -_cookie_search=re.compile("coding[:=]\s*([-\w.]+)").search - -def _detect_encoding(lines): - """Get the encoding of a Python source file from a list of lines as bytes - - This function does less than tokenize.detect_encoding added in Python 3 as - it does not attempt to raise a SyntaxError when the interpreter would, it - just wants the encoding of a source file Python has already compiled and - determined is valid. - """ - if not lines: - return _default_source_encoding - if lines[0].startswith("\xef\xbb\xbf"): - # Source starting with UTF-8 BOM is either UTF-8 or a SyntaxError - return "utf-8" - # Only the first two lines of the source file are examined - magic = _cookie_search("".join(lines[:2])) - if magic is None: - return _default_source_encoding - encoding = magic.group(1) - try: - codecs.lookup(encoding) - except LookupError: - # Some codecs raise something other than LookupError if they don't - # support the given error handler, but not the text ones that could - # actually be used for Python source code - return _default_source_encoding - return encoding - - -class _EncodingTuple(tuple): - """A tuple type that can have an encoding attribute smuggled on""" - - -def _get_source_encoding(filename): - """Detect, cache and return the encoding of Python source at filename""" - try: - return linecache.cache[filename].encoding - except (AttributeError, KeyError): - encoding = _detect_encoding(linecache.getlines(filename)) - if filename in linecache.cache: - newtuple = _EncodingTuple(linecache.cache[filename]) - newtuple.encoding = encoding - linecache.cache[filename] = newtuple - return encoding - - -def _get_exception_encoding(): - """Return the encoding we expect messages from the OS to be encoded in""" - if os.name == "nt": - # GZ 2010-05-24: Really want the codepage number instead, the error - # handling of standard codecs is more deterministic - return "mbcs" - # GZ 2010-05-23: We need this call to be after initialisation, but there's - # no benefit in asking more than once as it's a global - # setting that can change after the message is formatted. - return locale.getlocale(locale.LC_MESSAGES)[1] or "ascii" - - -def _exception_to_text(evalue): - """Try hard to get a sensible text value out of an exception instance""" - try: - return unicode(evalue) - except KeyboardInterrupt: - raise - except: - # Apparently this is what traceback._some_str does. Sigh - RBC 20100623 - pass - try: - return str(evalue).decode(_get_exception_encoding(), "replace") - except KeyboardInterrupt: - raise - except: - # Apparently this is what traceback._some_str does. Sigh - RBC 20100623 - pass - # Okay, out of ideas, let higher level handle it - return None - - -# GZ 2010-05-23: This function is huge and horrible and I welcome suggestions -# on the best way to break it up -_TB_HEADER = _u('Traceback (most recent call last):\n') -def _format_exc_info(eclass, evalue, tb, limit=None): - """Format a stack trace and the exception information as unicode - - Compatibility function for Python 2 which ensures each component of a - traceback is correctly decoded according to its origins. - - Based on traceback.format_exception and related functions. - """ - fs_enc = sys.getfilesystemencoding() - if tb: - list = [_TB_HEADER] - extracted_list = [] - for filename, lineno, name, line in traceback.extract_tb(tb, limit): - extracted_list.append(( - filename.decode(fs_enc, "replace"), - lineno, - name.decode("ascii", "replace"), - line and line.decode( - _get_source_encoding(filename), "replace"))) - list.extend(traceback.format_list(extracted_list)) - else: - list = [] - if evalue is None: - # Is a (deprecated) string exception - list.append((eclass + "\n").decode("ascii", "replace")) - return list - if isinstance(evalue, SyntaxError): - # Avoid duplicating the special formatting for SyntaxError here, - # instead create a new instance with unicode filename and line - # Potentially gives duff spacing, but that's a pre-existing issue - try: - msg, (filename, lineno, offset, line) = evalue - except (TypeError, ValueError): - pass # Strange exception instance, fall through to generic code - else: - # Errors during parsing give the line from buffer encoded as - # latin-1 or utf-8 or the encoding of the file depending on the - # coding and whether the patch for issue #1031213 is applied, so - # give up on trying to decode it and just read the file again - if line: - bytestr = linecache.getline(filename, lineno) - if bytestr: - if lineno == 1 and bytestr.startswith("\xef\xbb\xbf"): - bytestr = bytestr[3:] - line = bytestr.decode( - _get_source_encoding(filename), "replace") - del linecache.cache[filename] - else: - line = line.decode("ascii", "replace") - if filename: - filename = filename.decode(fs_enc, "replace") - evalue = eclass(msg, (filename, lineno, offset, line)) - list.extend(traceback.format_exception_only(eclass, evalue)) - return list - sclass = eclass.__name__ - svalue = _exception_to_text(evalue) - if svalue: - list.append("%s: %s\n" % (sclass, svalue)) - elif svalue is None: - # GZ 2010-05-24: Not a great fallback message, but keep for the moment - list.append("%s: \n" % (sclass, sclass)) - else: - list.append("%s\n" % sclass) - return list diff --git a/lib/testtools/testtools/content.py b/lib/testtools/testtools/content.py deleted file mode 100644 index 8bd4a228ed9..00000000000 --- a/lib/testtools/testtools/content.py +++ /dev/null @@ -1,324 +0,0 @@ -# Copyright (c) 2009-2012 testtools developers. See LICENSE for details. - -"""Content - a MIME-like Content object.""" - -__all__ = [ - 'attach_file', - 'Content', - 'content_from_file', - 'content_from_stream', - 'text_content', - 'TracebackContent', - ] - -import codecs -import json -import os -import sys -import traceback - -from testtools import try_import -from testtools.compat import _b, _format_exc_info, str_is_unicode, _u -from testtools.content_type import ContentType, JSON, UTF8_TEXT - - -functools = try_import('functools') - -_join_b = _b("").join - - -DEFAULT_CHUNK_SIZE = 4096 - -STDOUT_LINE = '\nStdout:\n%s' -STDERR_LINE = '\nStderr:\n%s' - - -def _iter_chunks(stream, chunk_size, seek_offset=None, seek_whence=0): - """Read 'stream' in chunks of 'chunk_size'. - - :param stream: A file-like object to read from. - :param chunk_size: The size of each read from 'stream'. - :param seek_offset: If non-None, seek before iterating. - :param seek_whence: Pass through to the seek call, if seeking. - """ - if seek_offset is not None: - stream.seek(seek_offset, seek_whence) - chunk = stream.read(chunk_size) - while chunk: - yield chunk - chunk = stream.read(chunk_size) - - -class Content(object): - """A MIME-like Content object. - - Content objects can be serialised to bytes using the iter_bytes method. - If the Content-Type is recognised by other code, they are welcome to - look for richer contents that mere byte serialisation - for example in - memory object graphs etc. However, such code MUST be prepared to receive - a generic Content object that has been reconstructed from a byte stream. - - :ivar content_type: The content type of this Content. - """ - - def __init__(self, content_type, get_bytes): - """Create a ContentType.""" - if None in (content_type, get_bytes): - raise ValueError("None not permitted in %r, %r" % ( - content_type, get_bytes)) - self.content_type = content_type - self._get_bytes = get_bytes - - def __eq__(self, other): - return (self.content_type == other.content_type and - _join_b(self.iter_bytes()) == _join_b(other.iter_bytes())) - - def as_text(self): - """Return all of the content as text. - - This is only valid where ``iter_text`` is. It will load all of the - content into memory. Where this is a concern, use ``iter_text`` - instead. - """ - return _u('').join(self.iter_text()) - - def iter_bytes(self): - """Iterate over bytestrings of the serialised content.""" - return self._get_bytes() - - def iter_text(self): - """Iterate over the text of the serialised content. - - This is only valid for text MIME types, and will use ISO-8859-1 if - no charset parameter is present in the MIME type. (This is somewhat - arbitrary, but consistent with RFC2617 3.7.1). - - :raises ValueError: If the content type is not text/\*. - """ - if self.content_type.type != "text": - raise ValueError("Not a text type %r" % self.content_type) - return self._iter_text() - - def _iter_text(self): - """Worker for iter_text - does the decoding.""" - encoding = self.content_type.parameters.get('charset', 'ISO-8859-1') - try: - # 2.5+ - decoder = codecs.getincrementaldecoder(encoding)() - for bytes in self.iter_bytes(): - yield decoder.decode(bytes) - final = decoder.decode(_b(''), True) - if final: - yield final - except AttributeError: - # < 2.5 - bytes = ''.join(self.iter_bytes()) - yield bytes.decode(encoding) - - def __repr__(self): - return "" % ( - self.content_type, _join_b(self.iter_bytes())) - - -class TracebackContent(Content): - """Content object for tracebacks. - - This adapts an exc_info tuple to the Content interface. - text/x-traceback;language=python is used for the mime type, in order to - provide room for other languages to format their tracebacks differently. - """ - - # Whether or not to hide layers of the stack trace that are - # unittest/testtools internal code. Defaults to True since the - # system-under-test is rarely unittest or testtools. - HIDE_INTERNAL_STACK = True - - def __init__(self, err, test): - """Create a TracebackContent for err.""" - if err is None: - raise ValueError("err may not be None") - content_type = ContentType('text', 'x-traceback', - {"language": "python", "charset": "utf8"}) - value = self._exc_info_to_unicode(err, test) - super(TracebackContent, self).__init__( - content_type, lambda: [value.encode("utf8")]) - - def _exc_info_to_unicode(self, err, test): - """Converts a sys.exc_info()-style tuple of values into a string. - - Copied from Python 2.7's unittest.TestResult._exc_info_to_string. - """ - exctype, value, tb = err - # Skip test runner traceback levels - if self.HIDE_INTERNAL_STACK: - while tb and self._is_relevant_tb_level(tb): - tb = tb.tb_next - - # testtools customization. When str is unicode (e.g. IronPython, - # Python 3), traceback.format_exception returns unicode. For Python 2, - # it returns bytes. We need to guarantee unicode. - if str_is_unicode: - format_exception = traceback.format_exception - else: - format_exception = _format_exc_info - - if (self.HIDE_INTERNAL_STACK and test.failureException - and isinstance(value, test.failureException)): - # Skip assert*() traceback levels - length = self._count_relevant_tb_levels(tb) - msgLines = format_exception(exctype, value, tb, length) - else: - msgLines = format_exception(exctype, value, tb) - - if getattr(self, 'buffer', None): - output = sys.stdout.getvalue() - error = sys.stderr.getvalue() - if output: - if not output.endswith('\n'): - output += '\n' - msgLines.append(STDOUT_LINE % output) - if error: - if not error.endswith('\n'): - error += '\n' - msgLines.append(STDERR_LINE % error) - return ''.join(msgLines) - - def _is_relevant_tb_level(self, tb): - return '__unittest' in tb.tb_frame.f_globals - - def _count_relevant_tb_levels(self, tb): - length = 0 - while tb and not self._is_relevant_tb_level(tb): - length += 1 - tb = tb.tb_next - return length - - -def json_content(json_data): - """Create a JSON `Content` object from JSON-encodeable data.""" - data = json.dumps(json_data) - if str_is_unicode: - # The json module perversely returns native str not bytes - data = data.encode('utf8') - return Content(JSON, lambda: [data]) - - -def text_content(text): - """Create a `Content` object from some text. - - This is useful for adding details which are short strings. - """ - return Content(UTF8_TEXT, lambda: [text.encode('utf8')]) - - -def maybe_wrap(wrapper, func): - """Merge metadata for func into wrapper if functools is present.""" - if functools is not None: - wrapper = functools.update_wrapper(wrapper, func) - return wrapper - - -def content_from_file(path, content_type=None, chunk_size=DEFAULT_CHUNK_SIZE, - buffer_now=False, seek_offset=None, seek_whence=0): - """Create a `Content` object from a file on disk. - - Note that unless 'read_now' is explicitly passed in as True, the file - will only be read from when ``iter_bytes`` is called. - - :param path: The path to the file to be used as content. - :param content_type: The type of content. If not specified, defaults - to UTF8-encoded text/plain. - :param chunk_size: The size of chunks to read from the file. - Defaults to ``DEFAULT_CHUNK_SIZE``. - :param buffer_now: If True, read the file from disk now and keep it in - memory. Otherwise, only read when the content is serialized. - :param seek_offset: If non-None, seek within the stream before reading it. - :param seek_whence: If supplied, pass to stream.seek() when seeking. - """ - if content_type is None: - content_type = UTF8_TEXT - def reader(): - # This should be try:finally:, but python2.4 makes that hard. When - # We drop older python support we can make this use a context manager - # for maximum simplicity. - stream = open(path, 'rb') - for chunk in _iter_chunks(stream, chunk_size, seek_offset, seek_whence): - yield chunk - stream.close() - return content_from_reader(reader, content_type, buffer_now) - - -def content_from_stream(stream, content_type=None, - chunk_size=DEFAULT_CHUNK_SIZE, buffer_now=False, - seek_offset=None, seek_whence=0): - """Create a `Content` object from a file-like stream. - - Note that the stream will only be read from when ``iter_bytes`` is - called. - - :param stream: A file-like object to read the content from. The stream - is not closed by this function or the content object it returns. - :param content_type: The type of content. If not specified, defaults - to UTF8-encoded text/plain. - :param chunk_size: The size of chunks to read from the file. - Defaults to ``DEFAULT_CHUNK_SIZE``. - :param buffer_now: If True, reads from the stream right now. Otherwise, - only reads when the content is serialized. Defaults to False. - :param seek_offset: If non-None, seek within the stream before reading it. - :param seek_whence: If supplied, pass to stream.seek() when seeking. - """ - if content_type is None: - content_type = UTF8_TEXT - reader = lambda: _iter_chunks(stream, chunk_size, seek_offset, seek_whence) - return content_from_reader(reader, content_type, buffer_now) - - -def content_from_reader(reader, content_type, buffer_now): - """Create a Content object that will obtain the content from reader. - - :param reader: A callback to read the content. Should return an iterable of - bytestrings. - :param content_type: The content type to create. - :param buffer_now: If True the reader is evaluated immediately and - buffered. - """ - if content_type is None: - content_type = UTF8_TEXT - if buffer_now: - contents = list(reader()) - reader = lambda: contents - return Content(content_type, reader) - - -def attach_file(detailed, path, name=None, content_type=None, - chunk_size=DEFAULT_CHUNK_SIZE, buffer_now=True): - """Attach a file to this test as a detail. - - This is a convenience method wrapping around ``addDetail``. - - Note that unless 'read_now' is explicitly passed in as True, the file - *must* exist when the test result is called with the results of this - test, after the test has been torn down. - - :param detailed: An object with details - :param path: The path to the file to attach. - :param name: The name to give to the detail for the attached file. - :param content_type: The content type of the file. If not provided, - defaults to UTF8-encoded text/plain. - :param chunk_size: The size of chunks to read from the file. Defaults - to something sensible. - :param buffer_now: If False the file content is read when the content - object is evaluated rather than when attach_file is called. - Note that this may be after any cleanups that obj_with_details has, so - if the file is a temporary file disabling buffer_now may cause the file - to be read after it is deleted. To handle those cases, using - attach_file as a cleanup is recommended because it guarantees a - sequence for when the attach_file call is made:: - - detailed.addCleanup(attach_file, 'foo.txt', detailed) - """ - if name is None: - name = os.path.basename(path) - content_object = content_from_file( - path, content_type, chunk_size, buffer_now) - detailed.addDetail(name, content_object) diff --git a/lib/testtools/testtools/content_type.py b/lib/testtools/testtools/content_type.py deleted file mode 100644 index c4914088cd7..00000000000 --- a/lib/testtools/testtools/content_type.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) 2009-2012 testtools developers. See LICENSE for details. - -"""ContentType - a MIME Content Type.""" - - -class ContentType(object): - """A content type from http://www.iana.org/assignments/media-types/ - - :ivar type: The primary type, e.g. "text" or "application" - :ivar subtype: The subtype, e.g. "plain" or "octet-stream" - :ivar parameters: A dict of additional parameters specific to the - content type. - """ - - def __init__(self, primary_type, sub_type, parameters=None): - """Create a ContentType.""" - if None in (primary_type, sub_type): - raise ValueError("None not permitted in %r, %r" % ( - primary_type, sub_type)) - self.type = primary_type - self.subtype = sub_type - self.parameters = parameters or {} - - def __eq__(self, other): - if type(other) != ContentType: - return False - return self.__dict__ == other.__dict__ - - def __repr__(self): - if self.parameters: - params = '; ' - params += ', '.join( - sorted('%s="%s"' % (k, v) for k, v in self.parameters.items())) - else: - params = '' - return "%s/%s%s" % (self.type, self.subtype, params) - - -JSON = ContentType('application', 'json') - -UTF8_TEXT = ContentType('text', 'plain', {'charset': 'utf8'}) diff --git a/lib/testtools/testtools/deferredruntest.py b/lib/testtools/testtools/deferredruntest.py deleted file mode 100644 index cf33c06e277..00000000000 --- a/lib/testtools/testtools/deferredruntest.py +++ /dev/null @@ -1,336 +0,0 @@ -# Copyright (c) 2010 testtools developers. See LICENSE for details. - -"""Individual test case execution for tests that return Deferreds. - -This module is highly experimental and is liable to change in ways that cause -subtle failures in tests. Use at your own peril. -""" - -__all__ = [ - 'assert_fails_with', - 'AsynchronousDeferredRunTest', - 'AsynchronousDeferredRunTestForBrokenTwisted', - 'SynchronousDeferredRunTest', - ] - -import sys - -from testtools.compat import StringIO -from testtools.content import ( - Content, - text_content, - ) -from testtools.content_type import UTF8_TEXT -from testtools.runtest import RunTest -from testtools._spinner import ( - extract_result, - NoResultError, - Spinner, - TimeoutError, - trap_unhandled_errors, - ) - -from twisted.internet import defer -from twisted.python import log -from twisted.trial.unittest import _LogObserver - - -class _DeferredRunTest(RunTest): - """Base for tests that return Deferreds.""" - - def _got_user_failure(self, failure, tb_label='traceback'): - """We got a failure from user code.""" - return self._got_user_exception( - (failure.type, failure.value, failure.getTracebackObject()), - tb_label=tb_label) - - -class SynchronousDeferredRunTest(_DeferredRunTest): - """Runner for tests that return synchronous Deferreds.""" - - def _run_user(self, function, *args): - d = defer.maybeDeferred(function, *args) - d.addErrback(self._got_user_failure) - result = extract_result(d) - return result - - -def run_with_log_observers(observers, function, *args, **kwargs): - """Run 'function' with the given Twisted log observers.""" - real_observers = list(log.theLogPublisher.observers) - for observer in real_observers: - log.theLogPublisher.removeObserver(observer) - for observer in observers: - log.theLogPublisher.addObserver(observer) - try: - return function(*args, **kwargs) - finally: - for observer in observers: - log.theLogPublisher.removeObserver(observer) - for observer in real_observers: - log.theLogPublisher.addObserver(observer) - - -# Observer of the Twisted log that we install during tests. -_log_observer = _LogObserver() - - - -class AsynchronousDeferredRunTest(_DeferredRunTest): - """Runner for tests that return Deferreds that fire asynchronously. - - That is, this test runner assumes that the Deferreds will only fire if the - reactor is left to spin for a while. - - Do not rely too heavily on the nuances of the behaviour of this class. - What it does to the reactor is black magic, and if we can find nicer ways - of doing it we will gladly break backwards compatibility. - - This is highly experimental code. Use at your own risk. - """ - - def __init__(self, case, handlers=None, reactor=None, timeout=0.005, - debug=False): - """Construct an `AsynchronousDeferredRunTest`. - - :param case: The `TestCase` to run. - :param handlers: A list of exception handlers (ExceptionType, handler) - where 'handler' is a callable that takes a `TestCase`, a - ``testtools.TestResult`` and the exception raised. - :param reactor: The Twisted reactor to use. If not given, we use the - default reactor. - :param timeout: The maximum time allowed for running a test. The - default is 0.005s. - :param debug: Whether or not to enable Twisted's debugging. Use this - to get information about unhandled Deferreds and left-over - DelayedCalls. Defaults to False. - """ - super(AsynchronousDeferredRunTest, self).__init__(case, handlers) - if reactor is None: - from twisted.internet import reactor - self._reactor = reactor - self._timeout = timeout - self._debug = debug - - @classmethod - def make_factory(cls, reactor=None, timeout=0.005, debug=False): - """Make a factory that conforms to the RunTest factory interface.""" - # This is horrible, but it means that the return value of the method - # will be able to be assigned to a class variable *and* also be - # invoked directly. - class AsynchronousDeferredRunTestFactory: - def __call__(self, case, handlers=None): - return cls(case, handlers, reactor, timeout, debug) - return AsynchronousDeferredRunTestFactory() - - @defer.deferredGenerator - def _run_cleanups(self): - """Run the cleanups on the test case. - - We expect that the cleanups on the test case can also return - asynchronous Deferreds. As such, we take the responsibility for - running the cleanups, rather than letting TestCase do it. - """ - while self.case._cleanups: - f, args, kwargs = self.case._cleanups.pop() - d = defer.maybeDeferred(f, *args, **kwargs) - thing = defer.waitForDeferred(d) - yield thing - try: - thing.getResult() - except Exception: - exc_info = sys.exc_info() - self.case._report_traceback(exc_info) - last_exception = exc_info[1] - yield last_exception - - def _make_spinner(self): - """Make the `Spinner` to be used to run the tests.""" - return Spinner(self._reactor, debug=self._debug) - - def _run_deferred(self): - """Run the test, assuming everything in it is Deferred-returning. - - This should return a Deferred that fires with True if the test was - successful and False if the test was not successful. It should *not* - call addSuccess on the result, because there's reactor clean up that - we needs to be done afterwards. - """ - fails = [] - - def fail_if_exception_caught(exception_caught): - if self.exception_caught == exception_caught: - fails.append(None) - - def clean_up(ignored=None): - """Run the cleanups.""" - d = self._run_cleanups() - def clean_up_done(result): - if result is not None: - self._exceptions.append(result) - fails.append(None) - return d.addCallback(clean_up_done) - - def set_up_done(exception_caught): - """Set up is done, either clean up or run the test.""" - if self.exception_caught == exception_caught: - fails.append(None) - return clean_up() - else: - d = self._run_user(self.case._run_test_method, self.result) - d.addCallback(fail_if_exception_caught) - d.addBoth(tear_down) - return d - - def tear_down(ignored): - d = self._run_user(self.case._run_teardown, self.result) - d.addCallback(fail_if_exception_caught) - d.addBoth(clean_up) - return d - - d = self._run_user(self.case._run_setup, self.result) - d.addCallback(set_up_done) - d.addBoth(lambda ignored: len(fails) == 0) - return d - - def _log_user_exception(self, e): - """Raise 'e' and report it as a user exception.""" - try: - raise e - except e.__class__: - self._got_user_exception(sys.exc_info()) - - def _blocking_run_deferred(self, spinner): - try: - return trap_unhandled_errors( - spinner.run, self._timeout, self._run_deferred) - except NoResultError: - # We didn't get a result at all! This could be for any number of - # reasons, but most likely someone hit Ctrl-C during the test. - raise KeyboardInterrupt - except TimeoutError: - # The function took too long to run. - self._log_user_exception(TimeoutError(self.case, self._timeout)) - return False, [] - - def _run_core(self): - # Add an observer to trap all logged errors. - self.case.reactor = self._reactor - error_observer = _log_observer - full_log = StringIO() - full_observer = log.FileLogObserver(full_log) - spinner = self._make_spinner() - successful, unhandled = run_with_log_observers( - [error_observer.gotEvent, full_observer.emit], - self._blocking_run_deferred, spinner) - - self.case.addDetail( - 'twisted-log', Content(UTF8_TEXT, full_log.readlines)) - - logged_errors = error_observer.flushErrors() - for logged_error in logged_errors: - successful = False - self._got_user_failure(logged_error, tb_label='logged-error') - - if unhandled: - successful = False - for debug_info in unhandled: - f = debug_info.failResult - info = debug_info._getDebugTracebacks() - if info: - self.case.addDetail( - 'unhandled-error-in-deferred-debug', - text_content(info)) - self._got_user_failure(f, 'unhandled-error-in-deferred') - - junk = spinner.clear_junk() - if junk: - successful = False - self._log_user_exception(UncleanReactorError(junk)) - - if successful: - self.result.addSuccess(self.case, details=self.case.getDetails()) - - def _run_user(self, function, *args): - """Run a user-supplied function. - - This just makes sure that it returns a Deferred, regardless of how the - user wrote it. - """ - d = defer.maybeDeferred(function, *args) - return d.addErrback(self._got_user_failure) - - -class AsynchronousDeferredRunTestForBrokenTwisted(AsynchronousDeferredRunTest): - """Test runner that works around Twisted brokenness re reactor junk. - - There are many APIs within Twisted itself where a Deferred fires but - leaves cleanup work scheduled for the reactor to do. Arguably, many of - these are bugs. This runner iterates the reactor event loop a number of - times after every test, in order to shake out these buggy-but-commonplace - events. - """ - - def _make_spinner(self): - spinner = super( - AsynchronousDeferredRunTestForBrokenTwisted, self)._make_spinner() - spinner._OBLIGATORY_REACTOR_ITERATIONS = 2 - return spinner - - -def assert_fails_with(d, *exc_types, **kwargs): - """Assert that 'd' will fail with one of 'exc_types'. - - The normal way to use this is to return the result of 'assert_fails_with' - from your unit test. - - Note that this function is experimental and unstable. Use at your own - peril; expect the API to change. - - :param d: A Deferred that is expected to fail. - :param exc_types: The exception types that the Deferred is expected to - fail with. - :param failureException: An optional keyword argument. If provided, will - raise that exception instead of - ``testtools.TestCase.failureException``. - :return: A Deferred that will fail with an ``AssertionError`` if 'd' does - not fail with one of the exception types. - """ - failureException = kwargs.pop('failureException', None) - if failureException is None: - # Avoid circular imports. - from testtools import TestCase - failureException = TestCase.failureException - expected_names = ", ".join(exc_type.__name__ for exc_type in exc_types) - def got_success(result): - raise failureException( - "%s not raised (%r returned)" % (expected_names, result)) - def got_failure(failure): - if failure.check(*exc_types): - return failure.value - raise failureException("%s raised instead of %s:\n %s" % ( - failure.type.__name__, expected_names, failure.getTraceback())) - return d.addCallbacks(got_success, got_failure) - - -def flush_logged_errors(*error_types): - return _log_observer.flushErrors(*error_types) - - -class UncleanReactorError(Exception): - """Raised when the reactor has junk in it.""" - - def __init__(self, junk): - Exception.__init__(self, - "The reactor still thinks it needs to do things. Close all " - "connections, kill all processes and make sure all delayed " - "calls have either fired or been cancelled:\n%s" - % ''.join(map(self._get_junk_info, junk))) - - def _get_junk_info(self, junk): - from twisted.internet.base import DelayedCall - if isinstance(junk, DelayedCall): - ret = str(junk) - else: - ret = repr(junk) - return ' %s\n' % (ret,) diff --git a/lib/testtools/testtools/distutilscmd.py b/lib/testtools/testtools/distutilscmd.py deleted file mode 100644 index 91e14ca504f..00000000000 --- a/lib/testtools/testtools/distutilscmd.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright (c) 2010-2011 testtools developers . See LICENSE for details. - -"""Extensions to the standard Python unittest library.""" - -import sys - -from distutils.core import Command -from distutils.errors import DistutilsOptionError - -from testtools.run import TestProgram, TestToolsTestRunner - - -class TestCommand(Command): - """Command to run unit tests with testtools""" - - description = "run unit tests with testtools" - - user_options = [ - ('catch', 'c', "Catch ctrl-C and display results so far"), - ('buffer', 'b', "Buffer stdout and stderr during tests"), - ('failfast', 'f', "Stop on first fail or error"), - ('test-module=','m', "Run 'test_suite' in specified module"), - ('test-suite=','s', - "Test suite to run (e.g. 'some_module.test_suite')") - ] - - def __init__(self, dist): - Command.__init__(self, dist) - self.runner = TestToolsTestRunner(sys.stdout) - - - def initialize_options(self): - self.test_suite = None - self.test_module = None - self.catch = None - self.buffer = None - self.failfast = None - - def finalize_options(self): - if self.test_suite is None: - if self.test_module is None: - raise DistutilsOptionError( - "You must specify a module or a suite to run tests from") - else: - self.test_suite = self.test_module+".test_suite" - elif self.test_module: - raise DistutilsOptionError( - "You may specify a module or a suite, but not both") - self.test_args = [self.test_suite] - if self.verbose: - self.test_args.insert(0, '--verbose') - if self.buffer: - self.test_args.insert(0, '--buffer') - if self.catch: - self.test_args.insert(0, '--catch') - if self.failfast: - self.test_args.insert(0, '--failfast') - - def run(self): - self.program = TestProgram( - argv=self.test_args, testRunner=self.runner, stdout=sys.stdout, - exit=False) diff --git a/lib/testtools/testtools/helpers.py b/lib/testtools/testtools/helpers.py deleted file mode 100644 index 2595c1d6295..00000000000 --- a/lib/testtools/testtools/helpers.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright (c) 2010-2012 testtools developers. See LICENSE for details. - -__all__ = [ - 'safe_hasattr', - 'try_import', - 'try_imports', - ] - -import sys - - -def try_import(name, alternative=None, error_callback=None): - """Attempt to import ``name``. If it fails, return ``alternative``. - - When supporting multiple versions of Python or optional dependencies, it - is useful to be able to try to import a module. - - :param name: The name of the object to import, e.g. ``os.path`` or - ``os.path.join``. - :param alternative: The value to return if no module can be imported. - Defaults to None. - :param error_callback: If non-None, a callable that is passed the ImportError - when the module cannot be loaded. - """ - module_segments = name.split('.') - last_error = None - while module_segments: - module_name = '.'.join(module_segments) - try: - module = __import__(module_name) - except ImportError: - last_error = sys.exc_info()[1] - module_segments.pop() - continue - else: - break - else: - if last_error is not None and error_callback is not None: - error_callback(last_error) - return alternative - nonexistent = object() - for segment in name.split('.')[1:]: - module = getattr(module, segment, nonexistent) - if module is nonexistent: - if last_error is not None and error_callback is not None: - error_callback(last_error) - return alternative - return module - - -_RAISE_EXCEPTION = object() -def try_imports(module_names, alternative=_RAISE_EXCEPTION, error_callback=None): - """Attempt to import modules. - - Tries to import the first module in ``module_names``. If it can be - imported, we return it. If not, we go on to the second module and try - that. The process continues until we run out of modules to try. If none - of the modules can be imported, either raise an exception or return the - provided ``alternative`` value. - - :param module_names: A sequence of module names to try to import. - :param alternative: The value to return if no module can be imported. - If unspecified, we raise an ImportError. - :param error_callback: If None, called with the ImportError for *each* - module that fails to load. - :raises ImportError: If none of the modules can be imported and no - alternative value was specified. - """ - module_names = list(module_names) - for module_name in module_names: - module = try_import(module_name, error_callback=error_callback) - if module: - return module - if alternative is _RAISE_EXCEPTION: - raise ImportError( - "Could not import any of: %s" % ', '.join(module_names)) - return alternative - - -def safe_hasattr(obj, attr, _marker=object()): - """Does 'obj' have an attribute 'attr'? - - Use this rather than built-in hasattr, as the built-in swallows exceptions - in some versions of Python and behaves unpredictably with respect to - properties. - """ - return getattr(obj, attr, _marker) is not _marker - - -def map_values(function, dictionary): - """Map ``function`` across the values of ``dictionary``. - - :return: A dict with the same keys as ``dictionary``, where the value - of each key ``k`` is ``function(dictionary[k])``. - """ - return dict((k, function(dictionary[k])) for k in dictionary) - - -def filter_values(function, dictionary): - """Filter ``dictionary`` by its values using ``function``.""" - return dict((k, v) for k, v in dictionary.items() if function(v)) - - -def dict_subtract(a, b): - """Return the part of ``a`` that's not in ``b``.""" - return dict((k, a[k]) for k in set(a) - set(b)) - - -def list_subtract(a, b): - """Return a list ``a`` without the elements of ``b``. - - If a particular value is in ``a`` twice and ``b`` once then the returned - list then that value will appear once in the returned list. - """ - a_only = list(a) - for x in b: - if x in a_only: - a_only.remove(x) - return a_only diff --git a/lib/testtools/testtools/matchers/__init__.py b/lib/testtools/testtools/matchers/__init__.py deleted file mode 100644 index ce949fda4c2..00000000000 --- a/lib/testtools/testtools/matchers/__init__.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright (c) 2008-2012 testtools developers. See LICENSE for details. - -"""All the matchers. - -Matchers, a way to express complex assertions outside the testcase. - -Inspired by 'hamcrest'. - -Matcher provides the abstract API that all matchers need to implement. - -Bundled matchers are listed in __all__: a list can be obtained by running -$ python -c 'import testtools.matchers; print testtools.matchers.__all__' -""" - -__all__ = [ - 'AfterPreprocessing', - 'AllMatch', - 'Annotate', - 'Contains', - 'ContainsAll', - 'ContainedByDict', - 'ContainsDict', - 'DirContains', - 'DirExists', - 'DocTestMatches', - 'EndsWith', - 'Equals', - 'FileContains', - 'FileExists', - 'GreaterThan', - 'HasPermissions', - 'Is', - 'IsInstance', - 'KeysEqual', - 'LessThan', - 'MatchesAll', - 'MatchesAny', - 'MatchesDict', - 'MatchesException', - 'MatchesListwise', - 'MatchesPredicate', - 'MatchesRegex', - 'MatchesSetwise', - 'MatchesStructure', - 'NotEquals', - 'Not', - 'PathExists', - 'Raises', - 'raises', - 'SamePath', - 'StartsWith', - 'TarballContains', - ] - -from ._basic import ( - Contains, - EndsWith, - Equals, - GreaterThan, - Is, - IsInstance, - LessThan, - MatchesRegex, - NotEquals, - StartsWith, - ) -from ._datastructures import ( - ContainsAll, - MatchesListwise, - MatchesSetwise, - MatchesStructure, - ) -from ._dict import ( - ContainedByDict, - ContainsDict, - KeysEqual, - MatchesDict, - ) -from ._doctest import ( - DocTestMatches, - ) -from ._exception import ( - MatchesException, - Raises, - raises, - ) -from ._filesystem import ( - DirContains, - DirExists, - FileContains, - FileExists, - HasPermissions, - PathExists, - SamePath, - TarballContains, - ) -from ._higherorder import ( - AfterPreprocessing, - AllMatch, - Annotate, - MatchesAll, - MatchesAny, - MatchesPredicate, - Not, - ) - -# XXX: These are not explicitly included in __all__. It's unclear how much of -# the public interface they really are. -from ._impl import ( - Matcher, - Mismatch, - MismatchError, - ) diff --git a/lib/testtools/testtools/matchers/_basic.py b/lib/testtools/testtools/matchers/_basic.py deleted file mode 100644 index 44a47c566b0..00000000000 --- a/lib/testtools/testtools/matchers/_basic.py +++ /dev/null @@ -1,315 +0,0 @@ -# Copyright (c) 2009-2012 testtools developers. See LICENSE for details. - -__all__ = [ - 'Contains', - 'EndsWith', - 'Equals', - 'GreaterThan', - 'Is', - 'IsInstance', - 'LessThan', - 'MatchesRegex', - 'NotEquals', - 'StartsWith', - ] - -import operator -from pprint import pformat -import re - -from ..compat import ( - _isbytes, - istext, - str_is_unicode, - text_repr, - ) -from ..helpers import list_subtract -from ._higherorder import PostfixedMismatch -from ._impl import ( - Matcher, - Mismatch, - ) - - -def _format(thing): - """ - Blocks of text with newlines are formatted as triple-quote - strings. Everything else is pretty-printed. - """ - if istext(thing) or _isbytes(thing): - return text_repr(thing) - return pformat(thing) - - -class _BinaryComparison(object): - """Matcher that compares an object to another object.""" - - def __init__(self, expected): - self.expected = expected - - def __str__(self): - return "%s(%r)" % (self.__class__.__name__, self.expected) - - def match(self, other): - if self.comparator(other, self.expected): - return None - return _BinaryMismatch(self.expected, self.mismatch_string, other) - - def comparator(self, expected, other): - raise NotImplementedError(self.comparator) - - -class _BinaryMismatch(Mismatch): - """Two things did not match.""" - - def __init__(self, expected, mismatch_string, other): - self.expected = expected - self._mismatch_string = mismatch_string - self.other = other - - def describe(self): - left = repr(self.expected) - right = repr(self.other) - if len(left) + len(right) > 70: - return "%s:\nreference = %s\nactual = %s\n" % ( - self._mismatch_string, _format(self.expected), - _format(self.other)) - else: - return "%s %s %s" % (left, self._mismatch_string, right) - - -class Equals(_BinaryComparison): - """Matches if the items are equal.""" - - comparator = operator.eq - mismatch_string = '!=' - - -class NotEquals(_BinaryComparison): - """Matches if the items are not equal. - - In most cases, this is equivalent to ``Not(Equals(foo))``. The difference - only matters when testing ``__ne__`` implementations. - """ - - comparator = operator.ne - mismatch_string = '==' - - -class Is(_BinaryComparison): - """Matches if the items are identical.""" - - comparator = operator.is_ - mismatch_string = 'is not' - - -class LessThan(_BinaryComparison): - """Matches if the item is less than the matchers reference object.""" - - comparator = operator.__lt__ - mismatch_string = 'is not >' - - -class GreaterThan(_BinaryComparison): - """Matches if the item is greater than the matchers reference object.""" - - comparator = operator.__gt__ - mismatch_string = 'is not <' - - -class SameMembers(Matcher): - """Matches if two iterators have the same members. - - This is not the same as set equivalence. The two iterators must be of the - same length and have the same repetitions. - """ - - def __init__(self, expected): - super(SameMembers, self).__init__() - self.expected = expected - - def __str__(self): - return '%s(%r)' % (self.__class__.__name__, self.expected) - - def match(self, observed): - expected_only = list_subtract(self.expected, observed) - observed_only = list_subtract(observed, self.expected) - if expected_only == observed_only == []: - return - return PostfixedMismatch( - "\nmissing: %s\nextra: %s" % ( - _format(expected_only), _format(observed_only)), - _BinaryMismatch(self.expected, 'elements differ', observed)) - - -class DoesNotStartWith(Mismatch): - - def __init__(self, matchee, expected): - """Create a DoesNotStartWith Mismatch. - - :param matchee: the string that did not match. - :param expected: the string that 'matchee' was expected to start with. - """ - self.matchee = matchee - self.expected = expected - - def describe(self): - return "%s does not start with %s." % ( - text_repr(self.matchee), text_repr(self.expected)) - - -class StartsWith(Matcher): - """Checks whether one string starts with another.""" - - def __init__(self, expected): - """Create a StartsWith Matcher. - - :param expected: the string that matchees should start with. - """ - self.expected = expected - - def __str__(self): - return "StartsWith(%r)" % (self.expected,) - - def match(self, matchee): - if not matchee.startswith(self.expected): - return DoesNotStartWith(matchee, self.expected) - return None - - -class DoesNotEndWith(Mismatch): - - def __init__(self, matchee, expected): - """Create a DoesNotEndWith Mismatch. - - :param matchee: the string that did not match. - :param expected: the string that 'matchee' was expected to end with. - """ - self.matchee = matchee - self.expected = expected - - def describe(self): - return "%s does not end with %s." % ( - text_repr(self.matchee), text_repr(self.expected)) - - -class EndsWith(Matcher): - """Checks whether one string ends with another.""" - - def __init__(self, expected): - """Create a EndsWith Matcher. - - :param expected: the string that matchees should end with. - """ - self.expected = expected - - def __str__(self): - return "EndsWith(%r)" % (self.expected,) - - def match(self, matchee): - if not matchee.endswith(self.expected): - return DoesNotEndWith(matchee, self.expected) - return None - - -class IsInstance(object): - """Matcher that wraps isinstance.""" - - def __init__(self, *types): - self.types = tuple(types) - - def __str__(self): - return "%s(%s)" % (self.__class__.__name__, - ', '.join(type.__name__ for type in self.types)) - - def match(self, other): - if isinstance(other, self.types): - return None - return NotAnInstance(other, self.types) - - -class NotAnInstance(Mismatch): - - def __init__(self, matchee, types): - """Create a NotAnInstance Mismatch. - - :param matchee: the thing which is not an instance of any of types. - :param types: A tuple of the types which were expected. - """ - self.matchee = matchee - self.types = types - - def describe(self): - if len(self.types) == 1: - typestr = self.types[0].__name__ - else: - typestr = 'any of (%s)' % ', '.join(type.__name__ for type in - self.types) - return "'%s' is not an instance of %s" % (self.matchee, typestr) - - -class DoesNotContain(Mismatch): - - def __init__(self, matchee, needle): - """Create a DoesNotContain Mismatch. - - :param matchee: the object that did not contain needle. - :param needle: the needle that 'matchee' was expected to contain. - """ - self.matchee = matchee - self.needle = needle - - def describe(self): - return "%r not in %r" % (self.needle, self.matchee) - - -class Contains(Matcher): - """Checks whether something is contained in another thing.""" - - def __init__(self, needle): - """Create a Contains Matcher. - - :param needle: the thing that needs to be contained by matchees. - """ - self.needle = needle - - def __str__(self): - return "Contains(%r)" % (self.needle,) - - def match(self, matchee): - try: - if self.needle not in matchee: - return DoesNotContain(matchee, self.needle) - except TypeError: - # e.g. 1 in 2 will raise TypeError - return DoesNotContain(matchee, self.needle) - return None - - -class MatchesRegex(object): - """Matches if the matchee is matched by a regular expression.""" - - def __init__(self, pattern, flags=0): - self.pattern = pattern - self.flags = flags - - def __str__(self): - args = ['%r' % self.pattern] - flag_arg = [] - # dir() sorts the attributes for us, so we don't need to do it again. - for flag in dir(re): - if len(flag) == 1: - if self.flags & getattr(re, flag): - flag_arg.append('re.%s' % flag) - if flag_arg: - args.append('|'.join(flag_arg)) - return '%s(%s)' % (self.__class__.__name__, ', '.join(args)) - - def match(self, value): - if not re.match(self.pattern, value, self.flags): - pattern = self.pattern - if not isinstance(pattern, str_is_unicode and str or unicode): - pattern = pattern.decode("latin1") - pattern = pattern.encode("unicode_escape").decode("ascii") - return Mismatch("%r does not match /%s/" % ( - value, pattern.replace("\\\\", "\\"))) diff --git a/lib/testtools/testtools/matchers/_datastructures.py b/lib/testtools/testtools/matchers/_datastructures.py deleted file mode 100644 index 70de790738a..00000000000 --- a/lib/testtools/testtools/matchers/_datastructures.py +++ /dev/null @@ -1,228 +0,0 @@ -# Copyright (c) 2009-2012 testtools developers. See LICENSE for details. - -__all__ = [ - 'ContainsAll', - 'MatchesListwise', - 'MatchesSetwise', - 'MatchesStructure', - ] - -"""Matchers that operate with knowledge of Python data structures.""" - -from ..helpers import map_values -from ._higherorder import ( - Annotate, - MatchesAll, - MismatchesAll, - ) -from ._impl import Mismatch - - -def ContainsAll(items): - """Make a matcher that checks whether a list of things is contained - in another thing. - - The matcher effectively checks that the provided sequence is a subset of - the matchee. - """ - from ._basic import Contains - return MatchesAll(*map(Contains, items), first_only=False) - - -class MatchesListwise(object): - """Matches if each matcher matches the corresponding value. - - More easily explained by example than in words: - - >>> from ._basic import Equals - >>> MatchesListwise([Equals(1)]).match([1]) - >>> MatchesListwise([Equals(1), Equals(2)]).match([1, 2]) - >>> print (MatchesListwise([Equals(1), Equals(2)]).match([2, 1]).describe()) - Differences: [ - 1 != 2 - 2 != 1 - ] - >>> matcher = MatchesListwise([Equals(1), Equals(2)], first_only=True) - >>> print (matcher.match([3, 4]).describe()) - 1 != 3 - """ - - def __init__(self, matchers, first_only=False): - """Construct a MatchesListwise matcher. - - :param matchers: A list of matcher that the matched values must match. - :param first_only: If True, then only report the first mismatch, - otherwise report all of them. Defaults to False. - """ - self.matchers = matchers - self.first_only = first_only - - def match(self, values): - from ._basic import Equals - mismatches = [] - length_mismatch = Annotate( - "Length mismatch", Equals(len(self.matchers))).match(len(values)) - if length_mismatch: - mismatches.append(length_mismatch) - for matcher, value in zip(self.matchers, values): - mismatch = matcher.match(value) - if mismatch: - if self.first_only: - return mismatch - mismatches.append(mismatch) - if mismatches: - return MismatchesAll(mismatches) - - -class MatchesStructure(object): - """Matcher that matches an object structurally. - - 'Structurally' here means that attributes of the object being matched are - compared against given matchers. - - `fromExample` allows the creation of a matcher from a prototype object and - then modified versions can be created with `update`. - - `byEquality` creates a matcher in much the same way as the constructor, - except that the matcher for each of the attributes is assumed to be - `Equals`. - - `byMatcher` creates a similar matcher to `byEquality`, but you get to pick - the matcher, rather than just using `Equals`. - """ - - def __init__(self, **kwargs): - """Construct a `MatchesStructure`. - - :param kwargs: A mapping of attributes to matchers. - """ - self.kws = kwargs - - @classmethod - def byEquality(cls, **kwargs): - """Matches an object where the attributes equal the keyword values. - - Similar to the constructor, except that the matcher is assumed to be - Equals. - """ - from ._basic import Equals - return cls.byMatcher(Equals, **kwargs) - - @classmethod - def byMatcher(cls, matcher, **kwargs): - """Matches an object where the attributes match the keyword values. - - Similar to the constructor, except that the provided matcher is used - to match all of the values. - """ - return cls(**map_values(matcher, kwargs)) - - @classmethod - def fromExample(cls, example, *attributes): - from ._basic import Equals - kwargs = {} - for attr in attributes: - kwargs[attr] = Equals(getattr(example, attr)) - return cls(**kwargs) - - def update(self, **kws): - new_kws = self.kws.copy() - for attr, matcher in kws.items(): - if matcher is None: - new_kws.pop(attr, None) - else: - new_kws[attr] = matcher - return type(self)(**new_kws) - - def __str__(self): - kws = [] - for attr, matcher in sorted(self.kws.items()): - kws.append("%s=%s" % (attr, matcher)) - return "%s(%s)" % (self.__class__.__name__, ', '.join(kws)) - - def match(self, value): - matchers = [] - values = [] - for attr, matcher in sorted(self.kws.items()): - matchers.append(Annotate(attr, matcher)) - values.append(getattr(value, attr)) - return MatchesListwise(matchers).match(values) - - -class MatchesSetwise(object): - """Matches if all the matchers match elements of the value being matched. - - That is, each element in the 'observed' set must match exactly one matcher - from the set of matchers, with no matchers left over. - - The difference compared to `MatchesListwise` is that the order of the - matchings does not matter. - """ - - def __init__(self, *matchers): - self.matchers = matchers - - def match(self, observed): - remaining_matchers = set(self.matchers) - not_matched = [] - for value in observed: - for matcher in remaining_matchers: - if matcher.match(value) is None: - remaining_matchers.remove(matcher) - break - else: - not_matched.append(value) - if not_matched or remaining_matchers: - remaining_matchers = list(remaining_matchers) - # There are various cases that all should be reported somewhat - # differently. - - # There are two trivial cases: - # 1) There are just some matchers left over. - # 2) There are just some values left over. - - # Then there are three more interesting cases: - # 3) There are the same number of matchers and values left over. - # 4) There are more matchers left over than values. - # 5) There are more values left over than matchers. - - if len(not_matched) == 0: - if len(remaining_matchers) > 1: - msg = "There were %s matchers left over: " % ( - len(remaining_matchers),) - else: - msg = "There was 1 matcher left over: " - msg += ', '.join(map(str, remaining_matchers)) - return Mismatch(msg) - elif len(remaining_matchers) == 0: - if len(not_matched) > 1: - return Mismatch( - "There were %s values left over: %s" % ( - len(not_matched), not_matched)) - else: - return Mismatch( - "There was 1 value left over: %s" % ( - not_matched, )) - else: - common_length = min(len(remaining_matchers), len(not_matched)) - if common_length == 0: - raise AssertionError("common_length can't be 0 here") - if common_length > 1: - msg = "There were %s mismatches" % (common_length,) - else: - msg = "There was 1 mismatch" - if len(remaining_matchers) > len(not_matched): - extra_matchers = remaining_matchers[common_length:] - msg += " and %s extra matcher" % (len(extra_matchers), ) - if len(extra_matchers) > 1: - msg += "s" - msg += ': ' + ', '.join(map(str, extra_matchers)) - elif len(not_matched) > len(remaining_matchers): - extra_values = not_matched[common_length:] - msg += " and %s extra value" % (len(extra_values), ) - if len(extra_values) > 1: - msg += "s" - msg += ': ' + str(extra_values) - return Annotate( - msg, MatchesListwise(remaining_matchers[:common_length]) - ).match(not_matched[:common_length]) diff --git a/lib/testtools/testtools/matchers/_dict.py b/lib/testtools/testtools/matchers/_dict.py deleted file mode 100644 index ff05199e6c1..00000000000 --- a/lib/testtools/testtools/matchers/_dict.py +++ /dev/null @@ -1,259 +0,0 @@ -# Copyright (c) 2009-2012 testtools developers. See LICENSE for details. - -__all__ = [ - 'KeysEqual', - ] - -from ..helpers import ( - dict_subtract, - filter_values, - map_values, - ) -from ._higherorder import ( - AnnotatedMismatch, - PrefixedMismatch, - MismatchesAll, - ) -from ._impl import Matcher, Mismatch - - -def LabelledMismatches(mismatches, details=None): - """A collection of mismatches, each labelled.""" - return MismatchesAll( - (PrefixedMismatch(k, v) for (k, v) in sorted(mismatches.items())), - wrap=False) - - -class MatchesAllDict(Matcher): - """Matches if all of the matchers it is created with match. - - A lot like ``MatchesAll``, but takes a dict of Matchers and labels any - mismatches with the key of the dictionary. - """ - - def __init__(self, matchers): - super(MatchesAllDict, self).__init__() - self.matchers = matchers - - def __str__(self): - return 'MatchesAllDict(%s)' % (_format_matcher_dict(self.matchers),) - - def match(self, observed): - mismatches = {} - for label in self.matchers: - mismatches[label] = self.matchers[label].match(observed) - return _dict_to_mismatch( - mismatches, result_mismatch=LabelledMismatches) - - -class DictMismatches(Mismatch): - """A mismatch with a dict of child mismatches.""" - - def __init__(self, mismatches, details=None): - super(DictMismatches, self).__init__(None, details=details) - self.mismatches = mismatches - - def describe(self): - lines = ['{'] - lines.extend( - [' %r: %s,' % (key, mismatch.describe()) - for (key, mismatch) in sorted(self.mismatches.items())]) - lines.append('}') - return '\n'.join(lines) - - -def _dict_to_mismatch(data, to_mismatch=None, - result_mismatch=DictMismatches): - if to_mismatch: - data = map_values(to_mismatch, data) - mismatches = filter_values(bool, data) - if mismatches: - return result_mismatch(mismatches) - - -class _MatchCommonKeys(Matcher): - """Match on keys in a dictionary. - - Given a dictionary where the values are matchers, this will look for - common keys in the matched dictionary and match if and only if all common - keys match the given matchers. - - Thus:: - - >>> structure = {'a': Equals('x'), 'b': Equals('y')} - >>> _MatchCommonKeys(structure).match({'a': 'x', 'c': 'z'}) - None - """ - - def __init__(self, dict_of_matchers): - super(_MatchCommonKeys, self).__init__() - self._matchers = dict_of_matchers - - def _compare_dicts(self, expected, observed): - common_keys = set(expected.keys()) & set(observed.keys()) - mismatches = {} - for key in common_keys: - mismatch = expected[key].match(observed[key]) - if mismatch: - mismatches[key] = mismatch - return mismatches - - def match(self, observed): - mismatches = self._compare_dicts(self._matchers, observed) - if mismatches: - return DictMismatches(mismatches) - - -class _SubDictOf(Matcher): - """Matches if the matched dict only has keys that are in given dict.""" - - def __init__(self, super_dict, format_value=repr): - super(_SubDictOf, self).__init__() - self.super_dict = super_dict - self.format_value = format_value - - def match(self, observed): - excess = dict_subtract(observed, self.super_dict) - return _dict_to_mismatch( - excess, lambda v: Mismatch(self.format_value(v))) - - -class _SuperDictOf(Matcher): - """Matches if all of the keys in the given dict are in the matched dict. - """ - - def __init__(self, sub_dict, format_value=repr): - super(_SuperDictOf, self).__init__() - self.sub_dict = sub_dict - self.format_value = format_value - - def match(self, super_dict): - return _SubDictOf(super_dict, self.format_value).match(self.sub_dict) - - -def _format_matcher_dict(matchers): - return '{%s}' % ( - ', '.join(sorted('%r: %s' % (k, v) for k, v in matchers.items()))) - - -class _CombinedMatcher(Matcher): - """Many matchers labelled and combined into one uber-matcher. - - Subclass this and then specify a dict of matcher factories that take a - single 'expected' value and return a matcher. The subclass will match - only if all of the matchers made from factories match. - - Not **entirely** dissimilar from ``MatchesAll``. - """ - - matcher_factories = {} - - def __init__(self, expected): - super(_CombinedMatcher, self).__init__() - self._expected = expected - - def format_expected(self, expected): - return repr(expected) - - def __str__(self): - return '%s(%s)' % ( - self.__class__.__name__, self.format_expected(self._expected)) - - def match(self, observed): - matchers = dict( - (k, v(self._expected)) for k, v in self.matcher_factories.items()) - return MatchesAllDict(matchers).match(observed) - - -class MatchesDict(_CombinedMatcher): - """Match a dictionary exactly, by its keys. - - Specify a dictionary mapping keys (often strings) to matchers. This is - the 'expected' dict. Any dictionary that matches this must have exactly - the same keys, and the values must match the corresponding matchers in the - expected dict. - """ - - matcher_factories = { - 'Extra': _SubDictOf, - 'Missing': lambda m: _SuperDictOf(m, format_value=str), - 'Differences': _MatchCommonKeys, - } - - format_expected = lambda self, expected: _format_matcher_dict(expected) - - -class ContainsDict(_CombinedMatcher): - """Match a dictionary for that contains a specified sub-dictionary. - - Specify a dictionary mapping keys (often strings) to matchers. This is - the 'expected' dict. Any dictionary that matches this must have **at - least** these keys, and the values must match the corresponding matchers - in the expected dict. Dictionaries that have more keys will also match. - - In other words, any matching dictionary must contain the dictionary given - to the constructor. - - Does not check for strict sub-dictionary. That is, equal dictionaries - match. - """ - - matcher_factories = { - 'Missing': lambda m: _SuperDictOf(m, format_value=str), - 'Differences': _MatchCommonKeys, - } - - format_expected = lambda self, expected: _format_matcher_dict(expected) - - -class ContainedByDict(_CombinedMatcher): - """Match a dictionary for which this is a super-dictionary. - - Specify a dictionary mapping keys (often strings) to matchers. This is - the 'expected' dict. Any dictionary that matches this must have **only** - these keys, and the values must match the corresponding matchers in the - expected dict. Dictionaries that have fewer keys can also match. - - In other words, any matching dictionary must be contained by the - dictionary given to the constructor. - - Does not check for strict super-dictionary. That is, equal dictionaries - match. - """ - - matcher_factories = { - 'Extra': _SubDictOf, - 'Differences': _MatchCommonKeys, - } - - format_expected = lambda self, expected: _format_matcher_dict(expected) - - -class KeysEqual(Matcher): - """Checks whether a dict has particular keys.""" - - def __init__(self, *expected): - """Create a `KeysEqual` Matcher. - - :param expected: The keys the dict is expected to have. If a dict, - then we use the keys of that dict, if a collection, we assume it - is a collection of expected keys. - """ - super(KeysEqual, self).__init__() - try: - self.expected = expected.keys() - except AttributeError: - self.expected = list(expected) - - def __str__(self): - return "KeysEqual(%s)" % ', '.join(map(repr, self.expected)) - - def match(self, matchee): - from ._basic import _BinaryMismatch, Equals - expected = sorted(self.expected) - matched = Equals(expected).match(sorted(matchee.keys())) - if matched: - return AnnotatedMismatch( - 'Keys not equal', - _BinaryMismatch(expected, 'does not match', matchee)) - return None diff --git a/lib/testtools/testtools/matchers/_doctest.py b/lib/testtools/testtools/matchers/_doctest.py deleted file mode 100644 index 41f3c003e53..00000000000 --- a/lib/testtools/testtools/matchers/_doctest.py +++ /dev/null @@ -1,104 +0,0 @@ -# Copyright (c) 2009-2012 testtools developers. See LICENSE for details. - -__all__ = [ - 'DocTestMatches', - ] - -import doctest -import re - -from ..compat import str_is_unicode -from ._impl import Mismatch - - -class _NonManglingOutputChecker(doctest.OutputChecker): - """Doctest checker that works with unicode rather than mangling strings - - This is needed because current Python versions have tried to fix string - encoding related problems, but regressed the default behaviour with - unicode inputs in the process. - - In Python 2.6 and 2.7 ``OutputChecker.output_difference`` is was changed - to return a bytestring encoded as per ``sys.stdout.encoding``, or utf-8 if - that can't be determined. Worse, that encoding process happens in the - innocent looking `_indent` global function. Because the - `DocTestMismatch.describe` result may well not be destined for printing to - stdout, this is no good for us. To get a unicode return as before, the - method is monkey patched if ``doctest._encoding`` exists. - - Python 3 has a different problem. For some reason both inputs are encoded - to ascii with 'backslashreplace', making an escaped string matches its - unescaped form. Overriding the offending ``OutputChecker._toAscii`` method - is sufficient to revert this. - """ - - def _toAscii(self, s): - """Return ``s`` unchanged rather than mangling it to ascii""" - return s - - # Only do this overriding hackery if doctest has a broken _input function - if getattr(doctest, "_encoding", None) is not None: - from types import FunctionType as __F - __f = doctest.OutputChecker.output_difference.im_func - __g = dict(__f.func_globals) - def _indent(s, indent=4, _pattern=re.compile("^(?!$)", re.MULTILINE)): - """Prepend non-empty lines in ``s`` with ``indent`` number of spaces""" - return _pattern.sub(indent*" ", s) - __g["_indent"] = _indent - output_difference = __F(__f.func_code, __g, "output_difference") - del __F, __f, __g, _indent - - -class DocTestMatches(object): - """See if a string matches a doctest example.""" - - def __init__(self, example, flags=0): - """Create a DocTestMatches to match example. - - :param example: The example to match e.g. 'foo bar baz' - :param flags: doctest comparison flags to match on. e.g. - doctest.ELLIPSIS. - """ - if not example.endswith('\n'): - example += '\n' - self.want = example # required variable name by doctest. - self.flags = flags - self._checker = _NonManglingOutputChecker() - - def __str__(self): - if self.flags: - flagstr = ", flags=%d" % self.flags - else: - flagstr = "" - return 'DocTestMatches(%r%s)' % (self.want, flagstr) - - def _with_nl(self, actual): - result = self.want.__class__(actual) - if not result.endswith('\n'): - result += '\n' - return result - - def match(self, actual): - with_nl = self._with_nl(actual) - if self._checker.check_output(self.want, with_nl, self.flags): - return None - return DocTestMismatch(self, with_nl) - - def _describe_difference(self, with_nl): - return self._checker.output_difference(self, with_nl, self.flags) - - -class DocTestMismatch(Mismatch): - """Mismatch object for DocTestMatches.""" - - def __init__(self, matcher, with_nl): - self.matcher = matcher - self.with_nl = with_nl - - def describe(self): - s = self.matcher._describe_difference(self.with_nl) - if str_is_unicode or isinstance(s, unicode): - return s - # GZ 2011-08-24: This is actually pretty bogus, most C0 codes should - # be escaped, in addition to non-ascii bytes. - return s.decode("latin1").encode("ascii", "backslashreplace") diff --git a/lib/testtools/testtools/matchers/_exception.py b/lib/testtools/testtools/matchers/_exception.py deleted file mode 100644 index c120487d3c4..00000000000 --- a/lib/testtools/testtools/matchers/_exception.py +++ /dev/null @@ -1,124 +0,0 @@ -# Copyright (c) 2009-2012 testtools developers. See LICENSE for details. - -__all__ = [ - 'MatchesException', - 'Raises', - 'raises', - ] - -import sys - -from testtools.compat import ( - classtypes, - _error_repr, - isbaseexception, - istext, - ) -from ._basic import MatchesRegex -from ._higherorder import AfterPreproccessing -from ._impl import ( - Matcher, - Mismatch, - ) - - -class MatchesException(Matcher): - """Match an exc_info tuple against an exception instance or type.""" - - def __init__(self, exception, value_re=None): - """Create a MatchesException that will match exc_info's for exception. - - :param exception: Either an exception instance or type. - If an instance is given, the type and arguments of the exception - are checked. If a type is given only the type of the exception is - checked. If a tuple is given, then as with isinstance, any of the - types in the tuple matching is sufficient to match. - :param value_re: If 'exception' is a type, and the matchee exception - is of the right type, then match against this. If value_re is a - string, then assume value_re is a regular expression and match - the str() of the exception against it. Otherwise, assume value_re - is a matcher, and match the exception against it. - """ - Matcher.__init__(self) - self.expected = exception - if istext(value_re): - value_re = AfterPreproccessing(str, MatchesRegex(value_re), False) - self.value_re = value_re - self._is_instance = type(self.expected) not in classtypes() + (tuple,) - - def match(self, other): - if type(other) != tuple: - return Mismatch('%r is not an exc_info tuple' % other) - expected_class = self.expected - if self._is_instance: - expected_class = expected_class.__class__ - if not issubclass(other[0], expected_class): - return Mismatch('%r is not a %r' % (other[0], expected_class)) - if self._is_instance: - if other[1].args != self.expected.args: - return Mismatch('%s has different arguments to %s.' % ( - _error_repr(other[1]), _error_repr(self.expected))) - elif self.value_re is not None: - return self.value_re.match(other[1]) - - def __str__(self): - if self._is_instance: - return "MatchesException(%s)" % _error_repr(self.expected) - return "MatchesException(%s)" % repr(self.expected) - - -class Raises(Matcher): - """Match if the matchee raises an exception when called. - - Exceptions which are not subclasses of Exception propogate out of the - Raises.match call unless they are explicitly matched. - """ - - def __init__(self, exception_matcher=None): - """Create a Raises matcher. - - :param exception_matcher: Optional validator for the exception raised - by matchee. If supplied the exc_info tuple for the exception raised - is passed into that matcher. If no exception_matcher is supplied - then the simple fact of raising an exception is considered enough - to match on. - """ - self.exception_matcher = exception_matcher - - def match(self, matchee): - try: - result = matchee() - return Mismatch('%r returned %r' % (matchee, result)) - # Catch all exceptions: Raises() should be able to match a - # KeyboardInterrupt or SystemExit. - except: - exc_info = sys.exc_info() - if self.exception_matcher: - mismatch = self.exception_matcher.match(exc_info) - if not mismatch: - del exc_info - return - else: - mismatch = None - # The exception did not match, or no explicit matching logic was - # performed. If the exception is a non-user exception (that is, not - # a subclass of Exception on Python 2.5+) then propogate it. - if isbaseexception(exc_info[1]): - del exc_info - raise - return mismatch - - def __str__(self): - return 'Raises()' - - -def raises(exception): - """Make a matcher that checks that a callable raises an exception. - - This is a convenience function, exactly equivalent to:: - - return Raises(MatchesException(exception)) - - See `Raises` and `MatchesException` for more information. - """ - return Raises(MatchesException(exception)) diff --git a/lib/testtools/testtools/matchers/_filesystem.py b/lib/testtools/testtools/matchers/_filesystem.py deleted file mode 100644 index 54f749b1359..00000000000 --- a/lib/testtools/testtools/matchers/_filesystem.py +++ /dev/null @@ -1,192 +0,0 @@ -# Copyright (c) 2009-2012 testtools developers. See LICENSE for details. - -"""Matchers for things related to the filesystem.""" - -__all__ = [ - 'FileContains', - 'DirExists', - 'FileExists', - 'HasPermissions', - 'PathExists', - 'SamePath', - 'TarballContains', - ] - -import os -import tarfile - -from ._basic import Equals -from ._higherorder import ( - MatchesAll, - MatchesPredicate, - ) -from ._impl import ( - Matcher, - ) - - -def PathExists(): - """Matches if the given path exists. - - Use like this:: - - assertThat('/some/path', PathExists()) - """ - return MatchesPredicate(os.path.exists, "%s does not exist.") - - -def DirExists(): - """Matches if the path exists and is a directory.""" - return MatchesAll( - PathExists(), - MatchesPredicate(os.path.isdir, "%s is not a directory."), - first_only=True) - - -def FileExists(): - """Matches if the given path exists and is a file.""" - return MatchesAll( - PathExists(), - MatchesPredicate(os.path.isfile, "%s is not a file."), - first_only=True) - - -class DirContains(Matcher): - """Matches if the given directory contains files with the given names. - - That is, is the directory listing exactly equal to the given files? - """ - - def __init__(self, filenames=None, matcher=None): - """Construct a ``DirContains`` matcher. - - Can be used in a basic mode where the whole directory listing is - matched against an expected directory listing (by passing - ``filenames``). Can also be used in a more advanced way where the - whole directory listing is matched against an arbitrary matcher (by - passing ``matcher`` instead). - - :param filenames: If specified, match the sorted directory listing - against this list of filenames, sorted. - :param matcher: If specified, match the sorted directory listing - against this matcher. - """ - if filenames == matcher == None: - raise AssertionError( - "Must provide one of `filenames` or `matcher`.") - if None not in (filenames, matcher): - raise AssertionError( - "Must provide either `filenames` or `matcher`, not both.") - if filenames is None: - self.matcher = matcher - else: - self.matcher = Equals(sorted(filenames)) - - def match(self, path): - mismatch = DirExists().match(path) - if mismatch is not None: - return mismatch - return self.matcher.match(sorted(os.listdir(path))) - - -class FileContains(Matcher): - """Matches if the given file has the specified contents.""" - - def __init__(self, contents=None, matcher=None): - """Construct a ``FileContains`` matcher. - - Can be used in a basic mode where the file contents are compared for - equality against the expected file contents (by passing ``contents``). - Can also be used in a more advanced way where the file contents are - matched against an arbitrary matcher (by passing ``matcher`` instead). - - :param contents: If specified, match the contents of the file with - these contents. - :param matcher: If specified, match the contents of the file against - this matcher. - """ - if contents == matcher == None: - raise AssertionError( - "Must provide one of `contents` or `matcher`.") - if None not in (contents, matcher): - raise AssertionError( - "Must provide either `contents` or `matcher`, not both.") - if matcher is None: - self.matcher = Equals(contents) - else: - self.matcher = matcher - - def match(self, path): - mismatch = PathExists().match(path) - if mismatch is not None: - return mismatch - f = open(path) - try: - actual_contents = f.read() - return self.matcher.match(actual_contents) - finally: - f.close() - - def __str__(self): - return "File at path exists and contains %s" % self.contents - - -class HasPermissions(Matcher): - """Matches if a file has the given permissions. - - Permissions are specified and matched as a four-digit octal string. - """ - - def __init__(self, octal_permissions): - """Construct a HasPermissions matcher. - - :param octal_permissions: A four digit octal string, representing the - intended access permissions. e.g. '0775' for rwxrwxr-x. - """ - super(HasPermissions, self).__init__() - self.octal_permissions = octal_permissions - - def match(self, filename): - permissions = oct(os.stat(filename).st_mode)[-4:] - return Equals(self.octal_permissions).match(permissions) - - -class SamePath(Matcher): - """Matches if two paths are the same. - - That is, the paths are equal, or they point to the same file but in - different ways. The paths do not have to exist. - """ - - def __init__(self, path): - super(SamePath, self).__init__() - self.path = path - - def match(self, other_path): - f = lambda x: os.path.abspath(os.path.realpath(x)) - return Equals(f(self.path)).match(f(other_path)) - - -class TarballContains(Matcher): - """Matches if the given tarball contains the given paths. - - Uses TarFile.getnames() to get the paths out of the tarball. - """ - - def __init__(self, paths): - super(TarballContains, self).__init__() - self.paths = paths - self.path_matcher = Equals(sorted(self.paths)) - - def match(self, tarball_path): - # Open underlying file first to ensure it's always closed: - # - f = open(tarball_path, "rb") - try: - tarball = tarfile.open(tarball_path, fileobj=f) - try: - return self.path_matcher.match(sorted(tarball.getnames())) - finally: - tarball.close() - finally: - f.close() diff --git a/lib/testtools/testtools/matchers/_higherorder.py b/lib/testtools/testtools/matchers/_higherorder.py deleted file mode 100644 index 53c52b665b7..00000000000 --- a/lib/testtools/testtools/matchers/_higherorder.py +++ /dev/null @@ -1,289 +0,0 @@ -# Copyright (c) 2009-2012 testtools developers. See LICENSE for details. - -__all__ = [ - 'AfterPreprocessing', - 'AllMatch', - 'Annotate', - 'MatchesAny', - 'MatchesAll', - 'Not', - ] - -import types - -from ._impl import ( - Matcher, - Mismatch, - MismatchDecorator, - ) - - -class MatchesAny(object): - """Matches if any of the matchers it is created with match.""" - - def __init__(self, *matchers): - self.matchers = matchers - - def match(self, matchee): - results = [] - for matcher in self.matchers: - mismatch = matcher.match(matchee) - if mismatch is None: - return None - results.append(mismatch) - return MismatchesAll(results) - - def __str__(self): - return "MatchesAny(%s)" % ', '.join([ - str(matcher) for matcher in self.matchers]) - - -class MatchesAll(object): - """Matches if all of the matchers it is created with match.""" - - def __init__(self, *matchers, **options): - """Construct a MatchesAll matcher. - - Just list the component matchers as arguments in the ``*args`` - style. If you want only the first mismatch to be reported, past in - first_only=True as a keyword argument. By default, all mismatches are - reported. - """ - self.matchers = matchers - self.first_only = options.get('first_only', False) - - def __str__(self): - return 'MatchesAll(%s)' % ', '.join(map(str, self.matchers)) - - def match(self, matchee): - results = [] - for matcher in self.matchers: - mismatch = matcher.match(matchee) - if mismatch is not None: - if self.first_only: - return mismatch - results.append(mismatch) - if results: - return MismatchesAll(results) - else: - return None - - -class MismatchesAll(Mismatch): - """A mismatch with many child mismatches.""" - - def __init__(self, mismatches, wrap=True): - self.mismatches = mismatches - self._wrap = wrap - - def describe(self): - descriptions = [] - if self._wrap: - descriptions = ["Differences: ["] - for mismatch in self.mismatches: - descriptions.append(mismatch.describe()) - if self._wrap: - descriptions.append("]") - return '\n'.join(descriptions) - - -class Not(object): - """Inverts a matcher.""" - - def __init__(self, matcher): - self.matcher = matcher - - def __str__(self): - return 'Not(%s)' % (self.matcher,) - - def match(self, other): - mismatch = self.matcher.match(other) - if mismatch is None: - return MatchedUnexpectedly(self.matcher, other) - else: - return None - - -class MatchedUnexpectedly(Mismatch): - """A thing matched when it wasn't supposed to.""" - - def __init__(self, matcher, other): - self.matcher = matcher - self.other = other - - def describe(self): - return "%r matches %s" % (self.other, self.matcher) - - -class Annotate(object): - """Annotates a matcher with a descriptive string. - - Mismatches are then described as ': '. - """ - - def __init__(self, annotation, matcher): - self.annotation = annotation - self.matcher = matcher - - @classmethod - def if_message(cls, annotation, matcher): - """Annotate ``matcher`` only if ``annotation`` is non-empty.""" - if not annotation: - return matcher - return cls(annotation, matcher) - - def __str__(self): - return 'Annotate(%r, %s)' % (self.annotation, self.matcher) - - def match(self, other): - mismatch = self.matcher.match(other) - if mismatch is not None: - return AnnotatedMismatch(self.annotation, mismatch) - - -class PostfixedMismatch(MismatchDecorator): - """A mismatch annotated with a descriptive string.""" - - def __init__(self, annotation, mismatch): - super(PostfixedMismatch, self).__init__(mismatch) - self.annotation = annotation - self.mismatch = mismatch - - def describe(self): - return '%s: %s' % (self.original.describe(), self.annotation) - - -AnnotatedMismatch = PostfixedMismatch - - -class PrefixedMismatch(MismatchDecorator): - - def __init__(self, prefix, mismatch): - super(PrefixedMismatch, self).__init__(mismatch) - self.prefix = prefix - - def describe(self): - return '%s: %s' % (self.prefix, self.original.describe()) - - -class AfterPreprocessing(object): - """Matches if the value matches after passing through a function. - - This can be used to aid in creating trivial matchers as functions, for - example:: - - def PathHasFileContent(content): - def _read(path): - return open(path).read() - return AfterPreprocessing(_read, Equals(content)) - """ - - def __init__(self, preprocessor, matcher, annotate=True): - """Create an AfterPreprocessing matcher. - - :param preprocessor: A function called with the matchee before - matching. - :param matcher: What to match the preprocessed matchee against. - :param annotate: Whether or not to annotate the matcher with - something explaining how we transformed the matchee. Defaults - to True. - """ - self.preprocessor = preprocessor - self.matcher = matcher - self.annotate = annotate - - def _str_preprocessor(self): - if isinstance(self.preprocessor, types.FunctionType): - return '' % self.preprocessor.__name__ - return str(self.preprocessor) - - def __str__(self): - return "AfterPreprocessing(%s, %s)" % ( - self._str_preprocessor(), self.matcher) - - def match(self, value): - after = self.preprocessor(value) - if self.annotate: - matcher = Annotate( - "after %s on %r" % (self._str_preprocessor(), value), - self.matcher) - else: - matcher = self.matcher - return matcher.match(after) - - -# This is the old, deprecated. spelling of the name, kept for backwards -# compatibility. -AfterPreproccessing = AfterPreprocessing - - -class AllMatch(object): - """Matches if all provided values match the given matcher.""" - - def __init__(self, matcher): - self.matcher = matcher - - def __str__(self): - return 'AllMatch(%s)' % (self.matcher,) - - def match(self, values): - mismatches = [] - for value in values: - mismatch = self.matcher.match(value) - if mismatch: - mismatches.append(mismatch) - if mismatches: - return MismatchesAll(mismatches) - - -class AnyMatch(object): - """Matches if any of the provided values match the given matcher.""" - - def __init__(self, matcher): - self.matcher = matcher - - def __str__(self): - return 'AnyMatch(%s)' % (self.matcher,) - - def match(self, values): - mismatches = [] - for value in values: - mismatch = self.matcher.match(value) - if mismatch: - mismatches.append(mismatch) - else: - return None - return MismatchesAll(mismatches) - - -class MatchesPredicate(Matcher): - """Match if a given function returns True. - - It is reasonably common to want to make a very simple matcher based on a - function that you already have that returns True or False given a single - argument (i.e. a predicate function). This matcher makes it very easy to - do so. e.g.:: - - IsEven = MatchesPredicate(lambda x: x % 2 == 0, '%s is not even') - self.assertThat(4, IsEven) - """ - - def __init__(self, predicate, message): - """Create a ``MatchesPredicate`` matcher. - - :param predicate: A function that takes a single argument and returns - a value that will be interpreted as a boolean. - :param message: A message to describe a mismatch. It will be formatted - with '%' and be given whatever was passed to ``match()``. Thus, it - needs to contain exactly one thing like '%s', '%d' or '%f'. - """ - self.predicate = predicate - self.message = message - - def __str__(self): - return '%s(%r, %r)' % ( - self.__class__.__name__, self.predicate, self.message) - - def match(self, x): - if not self.predicate(x): - return Mismatch(self.message % x) diff --git a/lib/testtools/testtools/matchers/_impl.py b/lib/testtools/testtools/matchers/_impl.py deleted file mode 100644 index 36e5ee02218..00000000000 --- a/lib/testtools/testtools/matchers/_impl.py +++ /dev/null @@ -1,175 +0,0 @@ -# Copyright (c) 2009-2012 testtools developers. See LICENSE for details. - -"""Matchers, a way to express complex assertions outside the testcase. - -Inspired by 'hamcrest'. - -Matcher provides the abstract API that all matchers need to implement. - -Bundled matchers are listed in __all__: a list can be obtained by running -$ python -c 'import testtools.matchers; print testtools.matchers.__all__' -""" - -__all__ = [ - 'Matcher', - 'Mismatch', - 'MismatchDecorator', - 'MismatchError', - ] - -from testtools.compat import ( - _isbytes, - istext, - str_is_unicode, - text_repr - ) - - -class Matcher(object): - """A pattern matcher. - - A Matcher must implement match and __str__ to be used by - testtools.TestCase.assertThat. Matcher.match(thing) returns None when - thing is completely matched, and a Mismatch object otherwise. - - Matchers can be useful outside of test cases, as they are simply a - pattern matching language expressed as objects. - - testtools.matchers is inspired by hamcrest, but is pythonic rather than - a Java transcription. - """ - - def match(self, something): - """Return None if this matcher matches something, a Mismatch otherwise. - """ - raise NotImplementedError(self.match) - - def __str__(self): - """Get a sensible human representation of the matcher. - - This should include the parameters given to the matcher and any - state that would affect the matches operation. - """ - raise NotImplementedError(self.__str__) - - -class Mismatch(object): - """An object describing a mismatch detected by a Matcher.""" - - def __init__(self, description=None, details=None): - """Construct a `Mismatch`. - - :param description: A description to use. If not provided, - `Mismatch.describe` must be implemented. - :param details: Extra details about the mismatch. Defaults - to the empty dict. - """ - if description: - self._description = description - if details is None: - details = {} - self._details = details - - def describe(self): - """Describe the mismatch. - - This should be either a human-readable string or castable to a string. - In particular, is should either be plain ascii or unicode on Python 2, - and care should be taken to escape control characters. - """ - try: - return self._description - except AttributeError: - raise NotImplementedError(self.describe) - - def get_details(self): - """Get extra details about the mismatch. - - This allows the mismatch to provide extra information beyond the basic - description, including large text or binary files, or debugging internals - without having to force it to fit in the output of 'describe'. - - The testtools assertion assertThat will query get_details and attach - all its values to the test, permitting them to be reported in whatever - manner the test environment chooses. - - :return: a dict mapping names to Content objects. name is a string to - name the detail, and the Content object is the detail to add - to the result. For more information see the API to which items from - this dict are passed testtools.TestCase.addDetail. - """ - return getattr(self, '_details', {}) - - def __repr__(self): - return "" % ( - id(self), self.__dict__) - - -class MismatchError(AssertionError): - """Raised when a mismatch occurs.""" - - # This class exists to work around - # . It provides a - # guaranteed way of getting a readable exception, no matter what crazy - # characters are in the matchee, matcher or mismatch. - - def __init__(self, matchee, matcher, mismatch, verbose=False): - # Have to use old-style upcalling for Python 2.4 and 2.5 - # compatibility. - AssertionError.__init__(self) - self.matchee = matchee - self.matcher = matcher - self.mismatch = mismatch - self.verbose = verbose - - def __str__(self): - difference = self.mismatch.describe() - if self.verbose: - # GZ 2011-08-24: Smelly API? Better to take any object and special - # case text inside? - if istext(self.matchee) or _isbytes(self.matchee): - matchee = text_repr(self.matchee, multiline=False) - else: - matchee = repr(self.matchee) - return ( - 'Match failed. Matchee: %s\nMatcher: %s\nDifference: %s\n' - % (matchee, self.matcher, difference)) - else: - return difference - - if not str_is_unicode: - - __unicode__ = __str__ - - def __str__(self): - return self.__unicode__().encode("ascii", "backslashreplace") - - -class MismatchDecorator(object): - """Decorate a ``Mismatch``. - - Forwards all messages to the original mismatch object. Probably the best - way to use this is inherit from this class and then provide your own - custom decoration logic. - """ - - def __init__(self, original): - """Construct a `MismatchDecorator`. - - :param original: A `Mismatch` object to decorate. - """ - self.original = original - - def __repr__(self): - return '' % (self.original,) - - def describe(self): - return self.original.describe() - - def get_details(self): - return self.original.get_details() - - -# Signal that this is part of the testing framework, and that code from this -# should not normally appear in tracebacks. -__unittest = True diff --git a/lib/testtools/testtools/monkey.py b/lib/testtools/testtools/monkey.py deleted file mode 100644 index ba0ac8fd8bf..00000000000 --- a/lib/testtools/testtools/monkey.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright (c) 2010 testtools developers. See LICENSE for details. - -"""Helpers for monkey-patching Python code.""" - -__all__ = [ - 'MonkeyPatcher', - 'patch', - ] - - -class MonkeyPatcher(object): - """A set of monkey-patches that can be applied and removed all together. - - Use this to cover up attributes with new objects. Particularly useful for - testing difficult code. - """ - - # Marker used to indicate that the patched attribute did not exist on the - # object before we patched it. - _NO_SUCH_ATTRIBUTE = object() - - def __init__(self, *patches): - """Construct a `MonkeyPatcher`. - - :param patches: The patches to apply, each should be (obj, name, - new_value). Providing patches here is equivalent to calling - `add_patch`. - """ - # List of patches to apply in (obj, name, value). - self._patches_to_apply = [] - # List of the original values for things that have been patched. - # (obj, name, value) format. - self._originals = [] - for patch in patches: - self.add_patch(*patch) - - def add_patch(self, obj, name, value): - """Add a patch to overwrite 'name' on 'obj' with 'value'. - - The attribute C{name} on C{obj} will be assigned to C{value} when - C{patch} is called or during C{run_with_patches}. - - You can restore the original values with a call to restore(). - """ - self._patches_to_apply.append((obj, name, value)) - - def patch(self): - """Apply all of the patches that have been specified with `add_patch`. - - Reverse this operation using L{restore}. - """ - for obj, name, value in self._patches_to_apply: - original_value = getattr(obj, name, self._NO_SUCH_ATTRIBUTE) - self._originals.append((obj, name, original_value)) - setattr(obj, name, value) - - def restore(self): - """Restore all original values to any patched objects. - - If the patched attribute did not exist on an object before it was - patched, `restore` will delete the attribute so as to return the - object to its original state. - """ - while self._originals: - obj, name, value = self._originals.pop() - if value is self._NO_SUCH_ATTRIBUTE: - delattr(obj, name) - else: - setattr(obj, name, value) - - def run_with_patches(self, f, *args, **kw): - """Run 'f' with the given args and kwargs with all patches applied. - - Restores all objects to their original state when finished. - """ - self.patch() - try: - return f(*args, **kw) - finally: - self.restore() - - -def patch(obj, attribute, value): - """Set 'obj.attribute' to 'value' and return a callable to restore 'obj'. - - If 'attribute' is not set on 'obj' already, then the returned callable - will delete the attribute when called. - - :param obj: An object to monkey-patch. - :param attribute: The name of the attribute to patch. - :param value: The value to set 'obj.attribute' to. - :return: A nullary callable that, when run, will restore 'obj' to its - original state. - """ - patcher = MonkeyPatcher((obj, attribute, value)) - patcher.patch() - return patcher.restore diff --git a/lib/testtools/testtools/run.py b/lib/testtools/testtools/run.py deleted file mode 100755 index c417bd04cb3..00000000000 --- a/lib/testtools/testtools/run.py +++ /dev/null @@ -1,351 +0,0 @@ -# Copyright (c) 2009 testtools developers. See LICENSE for details. - -"""python -m testtools.run testspec [testspec...] - -Run some tests with the testtools extended API. - -For instance, to run the testtools test suite. - $ python -m testtools.run testtools.tests.test_suite -""" - -import os -import unittest -import sys - -from testtools import TextTestResult -from testtools.compat import classtypes, istext, unicode_output_stream -from testtools.testsuite import iterate_tests, sorted_tests - - -defaultTestLoader = unittest.defaultTestLoader -defaultTestLoaderCls = unittest.TestLoader - -if getattr(defaultTestLoader, 'discover', None) is None: - try: - import discover - defaultTestLoader = discover.DiscoveringTestLoader() - defaultTestLoaderCls = discover.DiscoveringTestLoader - have_discover = True - except ImportError: - have_discover = False -else: - have_discover = True - - -class TestToolsTestRunner(object): - """ A thunk object to support unittest.TestProgram.""" - - def __init__(self, verbosity=None, failfast=None, buffer=None): - """Create a TestToolsTestRunner. - - :param verbosity: Ignored. - :param failfast: Stop running tests at the first failure. - :param buffer: Ignored. - """ - self.failfast = failfast - - def run(self, test): - "Run the given test case or test suite." - result = TextTestResult( - unicode_output_stream(sys.stdout), failfast=self.failfast) - result.startTestRun() - try: - return test.run(result) - finally: - result.stopTestRun() - - -#################### -# Taken from python 2.7 and slightly modified for compatibility with -# older versions. Delete when 2.7 is the oldest supported version. -# Modifications: -# - Use have_discover to raise an error if the user tries to use -# discovery on an old version and doesn't have discover installed. -# - If --catch is given check that installHandler is available, as -# it won't be on old python versions. -# - print calls have been been made single-source python3 compatibile. -# - exception handling likewise. -# - The default help has been changed to USAGE_AS_MAIN and USAGE_FROM_MODULE -# removed. -# - A tweak has been added to detect 'python -m *.run' and use a -# better progName in that case. -# - self.module is more comprehensively set to None when being invoked from -# the commandline - __name__ is used as a sentinel value. -# - --list has been added which can list tests (should be upstreamed). -# - --load-list has been added which can reduce the tests used (should be -# upstreamed). -# - The limitation of using getopt is declared to the user. -# - http://bugs.python.org/issue16709 is worked around, by sorting tests when -# discover is used. - -FAILFAST = " -f, --failfast Stop on first failure\n" -CATCHBREAK = " -c, --catch Catch control-C and display results\n" -BUFFEROUTPUT = " -b, --buffer Buffer stdout and stderr during test runs\n" - -USAGE_AS_MAIN = """\ -Usage: %(progName)s [options] [tests] - -Options: - -h, --help Show this message - -v, --verbose Verbose output - -q, --quiet Minimal output - -l, --list List tests rather than executing them. - --load-list Specifies a file containing test ids, only tests matching - those ids are executed. -%(failfast)s%(catchbreak)s%(buffer)s -Examples: - %(progName)s test_module - run tests from test_module - %(progName)s module.TestClass - run tests from module.TestClass - %(progName)s module.Class.test_method - run specified test method - -All options must come before [tests]. [tests] can be a list of any number of -test modules, classes and test methods. - -Alternative Usage: %(progName)s discover [options] - -Options: - -v, --verbose Verbose output -%(failfast)s%(catchbreak)s%(buffer)s -s directory Directory to start discovery ('.' default) - -p pattern Pattern to match test files ('test*.py' default) - -t directory Top level directory of project (default to - start directory) - -l, --list List tests rather than executing them. - --load-list Specifies a file containing test ids, only tests matching - those ids are executed. - -For test discovery all test modules must be importable from the top -level directory of the project. -""" - - -class TestProgram(object): - """A command-line program that runs a set of tests; this is primarily - for making test modules conveniently executable. - """ - USAGE = USAGE_AS_MAIN - - # defaults for testing - failfast = catchbreak = buffer = progName = None - - def __init__(self, module=__name__, defaultTest=None, argv=None, - testRunner=None, testLoader=defaultTestLoader, - exit=True, verbosity=1, failfast=None, catchbreak=None, - buffer=None, stdout=None): - if module == __name__: - self.module = None - elif istext(module): - self.module = __import__(module) - for part in module.split('.')[1:]: - self.module = getattr(self.module, part) - else: - self.module = module - if argv is None: - argv = sys.argv - if stdout is None: - stdout = sys.stdout - - self.exit = exit - self.failfast = failfast - self.catchbreak = catchbreak - self.verbosity = verbosity - self.buffer = buffer - self.defaultTest = defaultTest - self.listtests = False - self.load_list = None - self.testRunner = testRunner - self.testLoader = testLoader - progName = argv[0] - if progName.endswith('%srun.py' % os.path.sep): - elements = progName.split(os.path.sep) - progName = '%s.run' % elements[-2] - else: - progName = os.path.basename(argv[0]) - self.progName = progName - self.parseArgs(argv) - if self.load_list: - # TODO: preserve existing suites (like testresources does in - # OptimisingTestSuite.add, but with a standard protocol). - # This is needed because the load_tests hook allows arbitrary - # suites, even if that is rarely used. - source = open(self.load_list, 'rb') - try: - lines = source.readlines() - finally: - source.close() - test_ids = set(line.strip().decode('utf-8') for line in lines) - filtered = unittest.TestSuite() - for test in iterate_tests(self.test): - if test.id() in test_ids: - filtered.addTest(test) - self.test = filtered - if not self.listtests: - self.runTests() - else: - for test in iterate_tests(self.test): - stdout.write('%s\n' % test.id()) - - def usageExit(self, msg=None): - if msg: - print(msg) - usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '', - 'buffer': ''} - if self.failfast != False: - usage['failfast'] = FAILFAST - if self.catchbreak != False: - usage['catchbreak'] = CATCHBREAK - if self.buffer != False: - usage['buffer'] = BUFFEROUTPUT - print(self.USAGE % usage) - sys.exit(2) - - def parseArgs(self, argv): - if len(argv) > 1 and argv[1].lower() == 'discover': - self._do_discovery(argv[2:]) - return - - import getopt - long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch', 'buffer', - 'list', 'load-list='] - try: - options, args = getopt.getopt(argv[1:], 'hHvqfcbl', long_opts) - for opt, value in options: - if opt in ('-h','-H','--help'): - self.usageExit() - if opt in ('-q','--quiet'): - self.verbosity = 0 - if opt in ('-v','--verbose'): - self.verbosity = 2 - if opt in ('-f','--failfast'): - if self.failfast is None: - self.failfast = True - # Should this raise an exception if -f is not valid? - if opt in ('-c','--catch'): - if self.catchbreak is None: - self.catchbreak = True - # Should this raise an exception if -c is not valid? - if opt in ('-b','--buffer'): - if self.buffer is None: - self.buffer = True - # Should this raise an exception if -b is not valid? - if opt in ('-l', '--list'): - self.listtests = True - if opt == '--load-list': - self.load_list = value - if len(args) == 0 and self.defaultTest is None: - # createTests will load tests from self.module - self.testNames = None - elif len(args) > 0: - self.testNames = args - else: - self.testNames = (self.defaultTest,) - self.createTests() - except getopt.error: - self.usageExit(sys.exc_info()[1]) - - def createTests(self): - if self.testNames is None: - self.test = self.testLoader.loadTestsFromModule(self.module) - else: - self.test = self.testLoader.loadTestsFromNames(self.testNames, - self.module) - - def _do_discovery(self, argv, Loader=defaultTestLoaderCls): - # handle command line args for test discovery - if not have_discover: - raise AssertionError("Unable to use discovery, must use python 2.7 " - "or greater, or install the discover package.") - self.progName = '%s discover' % self.progName - import optparse - parser = optparse.OptionParser() - parser.prog = self.progName - parser.add_option('-v', '--verbose', dest='verbose', default=False, - help='Verbose output', action='store_true') - if self.failfast != False: - parser.add_option('-f', '--failfast', dest='failfast', default=False, - help='Stop on first fail or error', - action='store_true') - if self.catchbreak != False: - parser.add_option('-c', '--catch', dest='catchbreak', default=False, - help='Catch ctrl-C and display results so far', - action='store_true') - if self.buffer != False: - parser.add_option('-b', '--buffer', dest='buffer', default=False, - help='Buffer stdout and stderr during tests', - action='store_true') - parser.add_option('-s', '--start-directory', dest='start', default='.', - help="Directory to start discovery ('.' default)") - parser.add_option('-p', '--pattern', dest='pattern', default='test*.py', - help="Pattern to match tests ('test*.py' default)") - parser.add_option('-t', '--top-level-directory', dest='top', default=None, - help='Top level directory of project (defaults to start directory)') - parser.add_option('-l', '--list', dest='listtests', default=False, action="store_true", - help='List tests rather than running them.') - parser.add_option('--load-list', dest='load_list', default=None, - help='Specify a filename containing the test ids to use.') - - options, args = parser.parse_args(argv) - if len(args) > 3: - self.usageExit() - - for name, value in zip(('start', 'pattern', 'top'), args): - setattr(options, name, value) - - # only set options from the parsing here - # if they weren't set explicitly in the constructor - if self.failfast is None: - self.failfast = options.failfast - if self.catchbreak is None: - self.catchbreak = options.catchbreak - if self.buffer is None: - self.buffer = options.buffer - self.listtests = options.listtests - self.load_list = options.load_list - - if options.verbose: - self.verbosity = 2 - - start_dir = options.start - pattern = options.pattern - top_level_dir = options.top - - loader = Loader() - # See http://bugs.python.org/issue16709 - # While sorting here is intrusive, its better than being random. - # Rules for the sort: - # - standard suites are flattened, and the resulting tests sorted by - # id. - # - non-standard suites are preserved as-is, and sorted into position - # by the first test found by iterating the suite. - # We do this by a DSU process: flatten and grab a key, sort, strip the - # keys. - loaded = loader.discover(start_dir, pattern, top_level_dir) - self.test = sorted_tests(loaded) - - def runTests(self): - if (self.catchbreak - and getattr(unittest, 'installHandler', None) is not None): - unittest.installHandler() - if self.testRunner is None: - self.testRunner = TestToolsTestRunner - if isinstance(self.testRunner, classtypes()): - try: - testRunner = self.testRunner(verbosity=self.verbosity, - failfast=self.failfast, - buffer=self.buffer) - except TypeError: - # didn't accept the verbosity, buffer or failfast arguments - testRunner = self.testRunner() - else: - # it is assumed to be a TestRunner instance - testRunner = self.testRunner - self.result = testRunner.run(self.test) - if self.exit: - sys.exit(not self.result.wasSuccessful()) -################ - -def main(argv, stdout): - program = TestProgram(argv=argv, testRunner=TestToolsTestRunner, - stdout=stdout) - -if __name__ == '__main__': - main(sys.argv, sys.stdout) diff --git a/lib/testtools/testtools/runtest.py b/lib/testtools/testtools/runtest.py deleted file mode 100644 index 507ad87c276..00000000000 --- a/lib/testtools/testtools/runtest.py +++ /dev/null @@ -1,205 +0,0 @@ -# Copyright (c) 2009-2010 testtools developers. See LICENSE for details. - -"""Individual test case execution.""" - -__all__ = [ - 'MultipleExceptions', - 'RunTest', - ] - -import sys - -from testtools.testresult import ExtendedToOriginalDecorator - - -class MultipleExceptions(Exception): - """Represents many exceptions raised from some operation. - - :ivar args: The sys.exc_info() tuples for each exception. - """ - - -class RunTest(object): - """An object to run a test. - - RunTest objects are used to implement the internal logic involved in - running a test. TestCase.__init__ stores _RunTest as the class of RunTest - to execute. Passing the runTest= parameter to TestCase.__init__ allows a - different RunTest class to be used to execute the test. - - Subclassing or replacing RunTest can be useful to add functionality to the - way that tests are run in a given project. - - :ivar case: The test case that is to be run. - :ivar result: The result object a case is reporting to. - :ivar handlers: A list of (ExceptionClass, handler_function) for - exceptions that should be caught if raised from the user - code. Exceptions that are caught are checked against this list in - first to last order. There is a catch-all of 'Exception' at the end - of the list, so to add a new exception to the list, insert it at the - front (which ensures that it will be checked before any existing base - classes in the list. If you add multiple exceptions some of which are - subclasses of each other, add the most specific exceptions last (so - they come before their parent classes in the list). - :ivar exception_caught: An object returned when _run_user catches an - exception. - :ivar _exceptions: A list of caught exceptions, used to do the single - reporting of error/failure/skip etc. - """ - - def __init__(self, case, handlers=None): - """Create a RunTest to run a case. - - :param case: A testtools.TestCase test case object. - :param handlers: Exception handlers for this RunTest. These are stored - in self.handlers and can be modified later if needed. - """ - self.case = case - self.handlers = handlers or [] - self.exception_caught = object() - self._exceptions = [] - - def run(self, result=None): - """Run self.case reporting activity to result. - - :param result: Optional testtools.TestResult to report activity to. - :return: The result object the test was run against. - """ - if result is None: - actual_result = self.case.defaultTestResult() - actual_result.startTestRun() - else: - actual_result = result - try: - return self._run_one(actual_result) - finally: - if result is None: - actual_result.stopTestRun() - - def _run_one(self, result): - """Run one test reporting to result. - - :param result: A testtools.TestResult to report activity to. - This result object is decorated with an ExtendedToOriginalDecorator - to ensure that the latest TestResult API can be used with - confidence by client code. - :return: The result object the test was run against. - """ - return self._run_prepared_result(ExtendedToOriginalDecorator(result)) - - def _run_prepared_result(self, result): - """Run one test reporting to result. - - :param result: A testtools.TestResult to report activity to. - :return: The result object the test was run against. - """ - result.startTest(self.case) - self.result = result - try: - self._exceptions = [] - self._run_core() - if self._exceptions: - # One or more caught exceptions, now trigger the test's - # reporting method for just one. - e = self._exceptions.pop() - for exc_class, handler in self.handlers: - if isinstance(e, exc_class): - handler(self.case, self.result, e) - break - finally: - result.stopTest(self.case) - return result - - def _run_core(self): - """Run the user supplied test code.""" - if self.exception_caught == self._run_user(self.case._run_setup, - self.result): - # Don't run the test method if we failed getting here. - self._run_cleanups(self.result) - return - # Run everything from here on in. If any of the methods raise an - # exception we'll have failed. - failed = False - try: - if self.exception_caught == self._run_user( - self.case._run_test_method, self.result): - failed = True - finally: - try: - if self.exception_caught == self._run_user( - self.case._run_teardown, self.result): - failed = True - finally: - try: - if self.exception_caught == self._run_user( - self._run_cleanups, self.result): - failed = True - finally: - if not failed: - self.result.addSuccess(self.case, - details=self.case.getDetails()) - - def _run_cleanups(self, result): - """Run the cleanups that have been added with addCleanup. - - See the docstring for addCleanup for more information. - - :return: None if all cleanups ran without error, - ``exception_caught`` if there was an error. - """ - failing = False - while self.case._cleanups: - function, arguments, keywordArguments = self.case._cleanups.pop() - got_exception = self._run_user( - function, *arguments, **keywordArguments) - if got_exception == self.exception_caught: - failing = True - if failing: - return self.exception_caught - - def _run_user(self, fn, *args, **kwargs): - """Run a user supplied function. - - Exceptions are processed by `_got_user_exception`. - - :return: Either whatever 'fn' returns or ``exception_caught`` if - 'fn' raised an exception. - """ - try: - return fn(*args, **kwargs) - except KeyboardInterrupt: - raise - except: - return self._got_user_exception(sys.exc_info()) - - def _got_user_exception(self, exc_info, tb_label='traceback'): - """Called when user code raises an exception. - - If 'exc_info' is a `MultipleExceptions`, then we recurse into it - unpacking the errors that it's made up from. - - :param exc_info: A sys.exc_info() tuple for the user error. - :param tb_label: An optional string label for the error. If - not specified, will default to 'traceback'. - :return: 'exception_caught' if we catch one of the exceptions that - have handlers in 'handlers', otherwise raise the error. - """ - if exc_info[0] is MultipleExceptions: - for sub_exc_info in exc_info[1].args: - self._got_user_exception(sub_exc_info, tb_label) - return self.exception_caught - try: - e = exc_info[1] - self.case.onException(exc_info, tb_label=tb_label) - finally: - del exc_info - for exc_class, handler in self.handlers: - if isinstance(e, exc_class): - self._exceptions.append(e) - return self.exception_caught - raise e - - -# Signal that this is part of the testing framework, and that code from this -# should not normally appear in tracebacks. -__unittest = True diff --git a/lib/testtools/testtools/tags.py b/lib/testtools/testtools/tags.py deleted file mode 100644 index b55bd38667b..00000000000 --- a/lib/testtools/testtools/tags.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (c) 2012 testtools developers. See LICENSE for details. - -"""Tag support.""" - - -class TagContext(object): - """A tag context.""" - - def __init__(self, parent=None): - """Create a new TagContext. - - :param parent: If provided, uses this as the parent context. Any tags - that are current on the parent at the time of construction are - current in this context. - """ - self.parent = parent - self._tags = set() - if parent: - self._tags.update(parent.get_current_tags()) - - def get_current_tags(self): - """Return any current tags.""" - return set(self._tags) - - def change_tags(self, new_tags, gone_tags): - """Change the tags on this context. - - :param new_tags: A set of tags to add to this context. - :param gone_tags: A set of tags to remove from this context. - :return: The tags now current on this context. - """ - self._tags.update(new_tags) - self._tags.difference_update(gone_tags) - return self.get_current_tags() diff --git a/lib/testtools/testtools/testcase.py b/lib/testtools/testtools/testcase.py deleted file mode 100644 index fc5f863bcfc..00000000000 --- a/lib/testtools/testtools/testcase.py +++ /dev/null @@ -1,798 +0,0 @@ -# Copyright (c) 2008-2011 testtools developers. See LICENSE for details. - -"""Test case related stuff.""" - -__metaclass__ = type -__all__ = [ - 'clone_test_with_new_id', - 'ExpectedException', - 'gather_details', - 'run_test_with', - 'skip', - 'skipIf', - 'skipUnless', - 'TestCase', - ] - -import copy -import itertools -import sys -import types -import unittest - -from testtools import ( - content, - try_import, - ) -from testtools.compat import ( - advance_iterator, - reraise, - ) -from testtools.matchers import ( - Annotate, - Contains, - Equals, - MatchesAll, - MatchesException, - MismatchError, - Is, - IsInstance, - Not, - Raises, - ) -from testtools.monkey import patch -from testtools.runtest import RunTest -from testtools.testresult import ( - ExtendedToOriginalDecorator, - TestResult, - ) - -wraps = try_import('functools.wraps') - -class TestSkipped(Exception): - """Raised within TestCase.run() when a test is skipped.""" -testSkipped = try_import('unittest2.case.SkipTest', TestSkipped) -TestSkipped = try_import('unittest.case.SkipTest', TestSkipped) - - -class _UnexpectedSuccess(Exception): - """An unexpected success was raised. - - Note that this exception is private plumbing in testtools' testcase - module. - """ -_UnexpectedSuccess = try_import( - 'unittest2.case._UnexpectedSuccess', _UnexpectedSuccess) -_UnexpectedSuccess = try_import( - 'unittest.case._UnexpectedSuccess', _UnexpectedSuccess) - -class _ExpectedFailure(Exception): - """An expected failure occured. - - Note that this exception is private plumbing in testtools' testcase - module. - """ -_ExpectedFailure = try_import( - 'unittest2.case._ExpectedFailure', _ExpectedFailure) -_ExpectedFailure = try_import( - 'unittest.case._ExpectedFailure', _ExpectedFailure) - - -def run_test_with(test_runner, **kwargs): - """Decorate a test as using a specific ``RunTest``. - - e.g.:: - - @run_test_with(CustomRunner, timeout=42) - def test_foo(self): - self.assertTrue(True) - - The returned decorator works by setting an attribute on the decorated - function. `TestCase.__init__` looks for this attribute when deciding on a - ``RunTest`` factory. If you wish to use multiple decorators on a test - method, then you must either make this one the top-most decorator, or you - must write your decorators so that they update the wrapping function with - the attributes of the wrapped function. The latter is recommended style - anyway. ``functools.wraps``, ``functools.wrapper`` and - ``twisted.python.util.mergeFunctionMetadata`` can help you do this. - - :param test_runner: A ``RunTest`` factory that takes a test case and an - optional list of exception handlers. See ``RunTest``. - :param kwargs: Keyword arguments to pass on as extra arguments to - 'test_runner'. - :return: A decorator to be used for marking a test as needing a special - runner. - """ - def decorator(function): - # Set an attribute on 'function' which will inform TestCase how to - # make the runner. - function._run_test_with = ( - lambda case, handlers=None: - test_runner(case, handlers=handlers, **kwargs)) - return function - return decorator - - -def _copy_content(content_object): - """Make a copy of the given content object. - - The content within ``content_object`` is iterated and saved. This is - useful when the source of the content is volatile, a log file in a - temporary directory for example. - - :param content_object: A `content.Content` instance. - :return: A `content.Content` instance with the same mime-type as - ``content_object`` and a non-volatile copy of its content. - """ - content_bytes = list(content_object.iter_bytes()) - content_callback = lambda: content_bytes - return content.Content(content_object.content_type, content_callback) - - -def gather_details(source_dict, target_dict): - """Merge the details from ``source_dict`` into ``target_dict``. - - :param source_dict: A dictionary of details will be gathered. - :param target_dict: A dictionary into which details will be gathered. - """ - for name, content_object in source_dict.items(): - new_name = name - disambiguator = itertools.count(1) - while new_name in target_dict: - new_name = '%s-%d' % (name, advance_iterator(disambiguator)) - name = new_name - target_dict[name] = _copy_content(content_object) - - -class TestCase(unittest.TestCase): - """Extensions to the basic TestCase. - - :ivar exception_handlers: Exceptions to catch from setUp, runTest and - tearDown. This list is able to be modified at any time and consists of - (exception_class, handler(case, result, exception_value)) pairs. - :cvar run_tests_with: A factory to make the ``RunTest`` to run tests with. - Defaults to ``RunTest``. The factory is expected to take a test case - and an optional list of exception handlers. - """ - - skipException = TestSkipped - - run_tests_with = RunTest - - def __init__(self, *args, **kwargs): - """Construct a TestCase. - - :param testMethod: The name of the method to run. - :keyword runTest: Optional class to use to execute the test. If not - supplied ``RunTest`` is used. The instance to be used is created - when run() is invoked, so will be fresh each time. Overrides - ``TestCase.run_tests_with`` if given. - """ - runTest = kwargs.pop('runTest', None) - super(TestCase, self).__init__(*args, **kwargs) - self._cleanups = [] - self._unique_id_gen = itertools.count(1) - # Generators to ensure unique traceback ids. Maps traceback label to - # iterators. - self._traceback_id_gens = {} - self.__setup_called = False - self.__teardown_called = False - # __details is lazy-initialized so that a constructed-but-not-run - # TestCase is safe to use with clone_test_with_new_id. - self.__details = None - test_method = self._get_test_method() - if runTest is None: - runTest = getattr( - test_method, '_run_test_with', self.run_tests_with) - self.__RunTest = runTest - self.__exception_handlers = [] - self.exception_handlers = [ - (self.skipException, self._report_skip), - (self.failureException, self._report_failure), - (_ExpectedFailure, self._report_expected_failure), - (_UnexpectedSuccess, self._report_unexpected_success), - (Exception, self._report_error), - ] - if sys.version_info < (2, 6): - # Catch old-style string exceptions with None as the instance - self.exception_handlers.append((type(None), self._report_error)) - - def __eq__(self, other): - eq = getattr(unittest.TestCase, '__eq__', None) - if eq is not None and not unittest.TestCase.__eq__(self, other): - return False - return self.__dict__ == other.__dict__ - - def __repr__(self): - # We add id to the repr because it makes testing testtools easier. - return "<%s id=0x%0x>" % (self.id(), id(self)) - - def addDetail(self, name, content_object): - """Add a detail to be reported with this test's outcome. - - For more details see pydoc testtools.TestResult. - - :param name: The name to give this detail. - :param content_object: The content object for this detail. See - testtools.content for more detail. - """ - if self.__details is None: - self.__details = {} - self.__details[name] = content_object - - def getDetails(self): - """Get the details dict that will be reported with this test's outcome. - - For more details see pydoc testtools.TestResult. - """ - if self.__details is None: - self.__details = {} - return self.__details - - def patch(self, obj, attribute, value): - """Monkey-patch 'obj.attribute' to 'value' while the test is running. - - If 'obj' has no attribute, then the monkey-patch will still go ahead, - and the attribute will be deleted instead of restored to its original - value. - - :param obj: The object to patch. Can be anything. - :param attribute: The attribute on 'obj' to patch. - :param value: The value to set 'obj.attribute' to. - """ - self.addCleanup(patch(obj, attribute, value)) - - def shortDescription(self): - return self.id() - - def skipTest(self, reason): - """Cause this test to be skipped. - - This raises self.skipException(reason). skipException is raised - to permit a skip to be triggered at any point (during setUp or the - testMethod itself). The run() method catches skipException and - translates that into a call to the result objects addSkip method. - - :param reason: The reason why the test is being skipped. This must - support being cast into a unicode string for reporting. - """ - raise self.skipException(reason) - - # skipTest is how python2.7 spells this. Sometime in the future - # This should be given a deprecation decorator - RBC 20100611. - skip = skipTest - - def _formatTypes(self, classOrIterable): - """Format a class or a bunch of classes for display in an error.""" - className = getattr(classOrIterable, '__name__', None) - if className is None: - className = ', '.join(klass.__name__ for klass in classOrIterable) - return className - - def addCleanup(self, function, *arguments, **keywordArguments): - """Add a cleanup function to be called after tearDown. - - Functions added with addCleanup will be called in reverse order of - adding after tearDown, or after setUp if setUp raises an exception. - - If a function added with addCleanup raises an exception, the error - will be recorded as a test error, and the next cleanup will then be - run. - - Cleanup functions are always called before a test finishes running, - even if setUp is aborted by an exception. - """ - self._cleanups.append((function, arguments, keywordArguments)) - - def addOnException(self, handler): - """Add a handler to be called when an exception occurs in test code. - - This handler cannot affect what result methods are called, and is - called before any outcome is called on the result object. An example - use for it is to add some diagnostic state to the test details dict - which is expensive to calculate and not interesting for reporting in - the success case. - - Handlers are called before the outcome (such as addFailure) that - the exception has caused. - - Handlers are called in first-added, first-called order, and if they - raise an exception, that will propogate out of the test running - machinery, halting test processing. As a result, do not call code that - may unreasonably fail. - """ - self.__exception_handlers.append(handler) - - def _add_reason(self, reason): - self.addDetail('reason', content.text_content(reason)) - - def assertEqual(self, expected, observed, message=''): - """Assert that 'expected' is equal to 'observed'. - - :param expected: The expected value. - :param observed: The observed value. - :param message: An optional message to include in the error. - """ - matcher = Equals(expected) - self.assertThat(observed, matcher, message) - - failUnlessEqual = assertEquals = assertEqual - - def assertIn(self, needle, haystack): - """Assert that needle is in haystack.""" - self.assertThat(haystack, Contains(needle)) - - def assertIsNone(self, observed, message=''): - """Assert that 'observed' is equal to None. - - :param observed: The observed value. - :param message: An optional message describing the error. - """ - matcher = Is(None) - self.assertThat(observed, matcher, message) - - def assertIsNotNone(self, observed, message=''): - """Assert that 'observed' is not equal to None. - - :param observed: The observed value. - :param message: An optional message describing the error. - """ - matcher = Not(Is(None)) - self.assertThat(observed, matcher, message) - - def assertIs(self, expected, observed, message=''): - """Assert that 'expected' is 'observed'. - - :param expected: The expected value. - :param observed: The observed value. - :param message: An optional message describing the error. - """ - matcher = Is(expected) - self.assertThat(observed, matcher, message) - - def assertIsNot(self, expected, observed, message=''): - """Assert that 'expected' is not 'observed'.""" - matcher = Not(Is(expected)) - self.assertThat(observed, matcher, message) - - def assertNotIn(self, needle, haystack): - """Assert that needle is not in haystack.""" - matcher = Not(Contains(needle)) - self.assertThat(haystack, matcher) - - def assertIsInstance(self, obj, klass, msg=None): - if isinstance(klass, tuple): - matcher = IsInstance(*klass) - else: - matcher = IsInstance(klass) - self.assertThat(obj, matcher, msg) - - def assertRaises(self, excClass, callableObj, *args, **kwargs): - """Fail unless an exception of class excClass is thrown - by callableObj when invoked with arguments args and keyword - arguments kwargs. If a different type of exception is - thrown, it will not be caught, and the test case will be - deemed to have suffered an error, exactly as for an - unexpected exception. - """ - class ReRaiseOtherTypes(object): - def match(self, matchee): - if not issubclass(matchee[0], excClass): - reraise(*matchee) - class CaptureMatchee(object): - def match(self, matchee): - self.matchee = matchee[1] - capture = CaptureMatchee() - matcher = Raises(MatchesAll(ReRaiseOtherTypes(), - MatchesException(excClass), capture)) - our_callable = Nullary(callableObj, *args, **kwargs) - self.assertThat(our_callable, matcher) - return capture.matchee - failUnlessRaises = assertRaises - - def assertThat(self, matchee, matcher, message='', verbose=False): - """Assert that matchee is matched by matcher. - - :param matchee: An object to match with matcher. - :param matcher: An object meeting the testtools.Matcher protocol. - :raises MismatchError: When matcher does not match thing. - """ - matcher = Annotate.if_message(message, matcher) - mismatch = matcher.match(matchee) - if not mismatch: - return - existing_details = self.getDetails() - for (name, content) in mismatch.get_details().items(): - full_name = name - suffix = 1 - while full_name in existing_details: - full_name = "%s-%d" % (name, suffix) - suffix += 1 - self.addDetail(full_name, content) - raise MismatchError(matchee, matcher, mismatch, verbose) - - def defaultTestResult(self): - return TestResult() - - def expectFailure(self, reason, predicate, *args, **kwargs): - """Check that a test fails in a particular way. - - If the test fails in the expected way, a KnownFailure is caused. If it - succeeds an UnexpectedSuccess is caused. - - The expected use of expectFailure is as a barrier at the point in a - test where the test would fail. For example: - >>> def test_foo(self): - >>> self.expectFailure("1 should be 0", self.assertNotEqual, 1, 0) - >>> self.assertEqual(1, 0) - - If in the future 1 were to equal 0, the expectFailure call can simply - be removed. This separation preserves the original intent of the test - while it is in the expectFailure mode. - """ - # TODO: implement with matchers. - self._add_reason(reason) - try: - predicate(*args, **kwargs) - except self.failureException: - # GZ 2010-08-12: Don't know how to avoid exc_info cycle as the new - # unittest _ExpectedFailure wants old traceback - exc_info = sys.exc_info() - try: - self._report_traceback(exc_info) - raise _ExpectedFailure(exc_info) - finally: - del exc_info - else: - raise _UnexpectedSuccess(reason) - - def getUniqueInteger(self): - """Get an integer unique to this test. - - Returns an integer that is guaranteed to be unique to this instance. - Use this when you need an arbitrary integer in your test, or as a - helper for custom anonymous factory methods. - """ - return advance_iterator(self._unique_id_gen) - - def getUniqueString(self, prefix=None): - """Get a string unique to this test. - - Returns a string that is guaranteed to be unique to this instance. Use - this when you need an arbitrary string in your test, or as a helper - for custom anonymous factory methods. - - :param prefix: The prefix of the string. If not provided, defaults - to the id of the tests. - :return: A bytestring of '-'. - """ - if prefix is None: - prefix = self.id() - return '%s-%d' % (prefix, self.getUniqueInteger()) - - def onException(self, exc_info, tb_label='traceback'): - """Called when an exception propogates from test code. - - :seealso addOnException: - """ - if exc_info[0] not in [ - TestSkipped, _UnexpectedSuccess, _ExpectedFailure]: - self._report_traceback(exc_info, tb_label=tb_label) - for handler in self.__exception_handlers: - handler(exc_info) - - @staticmethod - def _report_error(self, result, err): - result.addError(self, details=self.getDetails()) - - @staticmethod - def _report_expected_failure(self, result, err): - result.addExpectedFailure(self, details=self.getDetails()) - - @staticmethod - def _report_failure(self, result, err): - result.addFailure(self, details=self.getDetails()) - - @staticmethod - def _report_skip(self, result, err): - if err.args: - reason = err.args[0] - else: - reason = "no reason given." - self._add_reason(reason) - result.addSkip(self, details=self.getDetails()) - - def _report_traceback(self, exc_info, tb_label='traceback'): - id_gen = self._traceback_id_gens.setdefault( - tb_label, itertools.count(0)) - tb_id = advance_iterator(id_gen) - if tb_id: - tb_label = '%s-%d' % (tb_label, tb_id) - self.addDetail(tb_label, content.TracebackContent(exc_info, self)) - - @staticmethod - def _report_unexpected_success(self, result, err): - result.addUnexpectedSuccess(self, details=self.getDetails()) - - def run(self, result=None): - return self.__RunTest(self, self.exception_handlers).run(result) - - def _run_setup(self, result): - """Run the setUp function for this test. - - :param result: A testtools.TestResult to report activity to. - :raises ValueError: If the base class setUp is not called, a - ValueError is raised. - """ - ret = self.setUp() - if not self.__setup_called: - raise ValueError( - "TestCase.setUp was not called. Have you upcalled all the " - "way up the hierarchy from your setUp? e.g. Call " - "super(%s, self).setUp() from your setUp()." - % self.__class__.__name__) - return ret - - def _run_teardown(self, result): - """Run the tearDown function for this test. - - :param result: A testtools.TestResult to report activity to. - :raises ValueError: If the base class tearDown is not called, a - ValueError is raised. - """ - ret = self.tearDown() - if not self.__teardown_called: - raise ValueError( - "TestCase.tearDown was not called. Have you upcalled all the " - "way up the hierarchy from your tearDown? e.g. Call " - "super(%s, self).tearDown() from your tearDown()." - % self.__class__.__name__) - return ret - - def _get_test_method(self): - absent_attr = object() - # Python 2.5+ - method_name = getattr(self, '_testMethodName', absent_attr) - if method_name is absent_attr: - # Python 2.4 - method_name = getattr(self, '_TestCase__testMethodName') - return getattr(self, method_name) - - def _run_test_method(self, result): - """Run the test method for this test. - - :param result: A testtools.TestResult to report activity to. - :return: None. - """ - return self._get_test_method()() - - def useFixture(self, fixture): - """Use fixture in a test case. - - The fixture will be setUp, and self.addCleanup(fixture.cleanUp) called. - - :param fixture: The fixture to use. - :return: The fixture, after setting it up and scheduling a cleanup for - it. - """ - try: - fixture.setUp() - except: - gather_details(fixture.getDetails(), self.getDetails()) - raise - else: - self.addCleanup(fixture.cleanUp) - self.addCleanup( - gather_details, fixture.getDetails(), self.getDetails()) - return fixture - - def setUp(self): - super(TestCase, self).setUp() - self.__setup_called = True - - def tearDown(self): - super(TestCase, self).tearDown() - unittest.TestCase.tearDown(self) - self.__teardown_called = True - - -class PlaceHolder(object): - """A placeholder test. - - `PlaceHolder` implements much of the same interface as TestCase and is - particularly suitable for being added to TestResults. - """ - - failureException = None - - def __init__(self, test_id, short_description=None, details=None, - outcome='addSuccess', error=None): - """Construct a `PlaceHolder`. - - :param test_id: The id of the placeholder test. - :param short_description: The short description of the place holder - test. If not provided, the id will be used instead. - :param details: Outcome details as accepted by addSuccess etc. - :param outcome: The outcome to call. Defaults to 'addSuccess'. - """ - self._test_id = test_id - self._short_description = short_description - self._details = details or {} - self._outcome = outcome - if error is not None: - self._details['traceback'] = content.TracebackContent(error, self) - - def __call__(self, result=None): - return self.run(result=result) - - def __repr__(self): - internal = [self._outcome, self._test_id, self._details] - if self._short_description is not None: - internal.append(self._short_description) - return "<%s.%s(%s)>" % ( - self.__class__.__module__, - self.__class__.__name__, - ", ".join(map(repr, internal))) - - def __str__(self): - return self.id() - - def countTestCases(self): - return 1 - - def debug(self): - pass - - def id(self): - return self._test_id - - def _result(self, result): - if result is None: - return TestResult() - else: - return ExtendedToOriginalDecorator(result) - - def run(self, result=None): - result = self._result(result) - result.startTest(self) - outcome = getattr(result, self._outcome) - outcome(self, details=self._details) - result.stopTest(self) - - def shortDescription(self): - if self._short_description is None: - return self.id() - else: - return self._short_description - - -def ErrorHolder(test_id, error, short_description=None, details=None): - """Construct an `ErrorHolder`. - - :param test_id: The id of the test. - :param error: The exc info tuple that will be used as the test's error. - This is inserted into the details as 'traceback' - any existing key - will be overridden. - :param short_description: An optional short description of the test. - :param details: Outcome details as accepted by addSuccess etc. - """ - return PlaceHolder(test_id, short_description=short_description, - details=details, outcome='addError', error=error) - - -# Python 2.4 did not know how to copy functions. -if types.FunctionType not in copy._copy_dispatch: - copy._copy_dispatch[types.FunctionType] = copy._copy_immutable - - -def clone_test_with_new_id(test, new_id): - """Copy a `TestCase`, and give the copied test a new id. - - This is only expected to be used on tests that have been constructed but - not executed. - """ - newTest = copy.copy(test) - newTest.id = lambda: new_id - return newTest - - -def skip(reason): - """A decorator to skip unit tests. - - This is just syntactic sugar so users don't have to change any of their - unit tests in order to migrate to python 2.7, which provides the - @unittest.skip decorator. - """ - def decorator(test_item): - if wraps is not None: - @wraps(test_item) - def skip_wrapper(*args, **kwargs): - raise TestCase.skipException(reason) - else: - def skip_wrapper(test_item): - test_item.skip(reason) - return skip_wrapper - return decorator - - -def skipIf(condition, reason): - """Skip a test if the condition is true.""" - if condition: - return skip(reason) - def _id(obj): - return obj - return _id - - -def skipUnless(condition, reason): - """Skip a test unless the condition is true.""" - if not condition: - return skip(reason) - def _id(obj): - return obj - return _id - - -class ExpectedException: - """A context manager to handle expected exceptions. - - In Python 2.5 or later:: - - def test_foo(self): - with ExpectedException(ValueError, 'fo.*'): - raise ValueError('foo') - - will pass. If the raised exception has a type other than the specified - type, it will be re-raised. If it has a 'str()' that does not match the - given regular expression, an AssertionError will be raised. If no - exception is raised, an AssertionError will be raised. - """ - - def __init__(self, exc_type, value_re=None): - """Construct an `ExpectedException`. - - :param exc_type: The type of exception to expect. - :param value_re: A regular expression to match against the - 'str()' of the raised exception. - """ - self.exc_type = exc_type - self.value_re = value_re - - def __enter__(self): - pass - - def __exit__(self, exc_type, exc_value, traceback): - if exc_type is None: - raise AssertionError('%s not raised.' % self.exc_type.__name__) - if exc_type != self.exc_type: - return False - if self.value_re: - matcher = MatchesException(self.exc_type, self.value_re) - mismatch = matcher.match((exc_type, exc_value, traceback)) - if mismatch: - raise AssertionError(mismatch.describe()) - return True - - -class Nullary(object): - """Turn a callable into a nullary callable. - - The advantage of this over ``lambda: f(*args, **kwargs)`` is that it - preserves the ``repr()`` of ``f``. - """ - - def __init__(self, callable_object, *args, **kwargs): - self._callable_object = callable_object - self._args = args - self._kwargs = kwargs - - def __call__(self): - return self._callable_object(*self._args, **self._kwargs) - - def __repr__(self): - return repr(self._callable_object) - - -# Signal that this is part of the testing framework, and that code from this -# should not normally appear in tracebacks. -__unittest = True diff --git a/lib/testtools/testtools/testresult/__init__.py b/lib/testtools/testtools/testresult/__init__.py deleted file mode 100644 index d37a772cff7..00000000000 --- a/lib/testtools/testtools/testresult/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2009-2012 testtools developers. See LICENSE for details. - -"""Test result objects.""" - -__all__ = [ - 'ExtendedToOriginalDecorator', - 'MultiTestResult', - 'Tagger', - 'TestByTestResult', - 'TestResult', - 'TestResultDecorator', - 'TextTestResult', - 'ThreadsafeForwardingResult', - ] - -from testtools.testresult.real import ( - ExtendedToOriginalDecorator, - MultiTestResult, - Tagger, - TestByTestResult, - TestResult, - TestResultDecorator, - TextTestResult, - ThreadsafeForwardingResult, - ) diff --git a/lib/testtools/testtools/testresult/doubles.py b/lib/testtools/testtools/testresult/doubles.py deleted file mode 100644 index 1865e931eec..00000000000 --- a/lib/testtools/testtools/testresult/doubles.py +++ /dev/null @@ -1,150 +0,0 @@ -# Copyright (c) 2009-2010 testtools developers. See LICENSE for details. - -"""Doubles of test result objects, useful for testing unittest code.""" - -__all__ = [ - 'Python26TestResult', - 'Python27TestResult', - 'ExtendedTestResult', - ] - - -from testtools.tags import TagContext - - -class LoggingBase(object): - """Basic support for logging of results.""" - - def __init__(self): - self._events = [] - self.shouldStop = False - self._was_successful = True - self.testsRun = 0 - - -class Python26TestResult(LoggingBase): - """A precisely python 2.6 like test result, that logs.""" - - def addError(self, test, err): - self._was_successful = False - self._events.append(('addError', test, err)) - - def addFailure(self, test, err): - self._was_successful = False - self._events.append(('addFailure', test, err)) - - def addSuccess(self, test): - self._events.append(('addSuccess', test)) - - def startTest(self, test): - self._events.append(('startTest', test)) - self.testsRun += 1 - - def stop(self): - self.shouldStop = True - - def stopTest(self, test): - self._events.append(('stopTest', test)) - - def wasSuccessful(self): - return self._was_successful - - -class Python27TestResult(Python26TestResult): - """A precisely python 2.7 like test result, that logs.""" - - def __init__(self): - super(Python27TestResult, self).__init__() - self.failfast = False - - def addError(self, test, err): - super(Python27TestResult, self).addError(test, err) - if self.failfast: - self.stop() - - def addFailure(self, test, err): - super(Python27TestResult, self).addFailure(test, err) - if self.failfast: - self.stop() - - def addExpectedFailure(self, test, err): - self._events.append(('addExpectedFailure', test, err)) - - def addSkip(self, test, reason): - self._events.append(('addSkip', test, reason)) - - def addUnexpectedSuccess(self, test): - self._events.append(('addUnexpectedSuccess', test)) - if self.failfast: - self.stop() - - def startTestRun(self): - self._events.append(('startTestRun',)) - - def stopTestRun(self): - self._events.append(('stopTestRun',)) - - -class ExtendedTestResult(Python27TestResult): - """A test result like the proposed extended unittest result API.""" - - def __init__(self): - super(ExtendedTestResult, self).__init__() - self._tags = TagContext() - - def addError(self, test, err=None, details=None): - self._was_successful = False - self._events.append(('addError', test, err or details)) - - def addFailure(self, test, err=None, details=None): - self._was_successful = False - self._events.append(('addFailure', test, err or details)) - - def addExpectedFailure(self, test, err=None, details=None): - self._events.append(('addExpectedFailure', test, err or details)) - - def addSkip(self, test, reason=None, details=None): - self._events.append(('addSkip', test, reason or details)) - - def addSuccess(self, test, details=None): - if details: - self._events.append(('addSuccess', test, details)) - else: - self._events.append(('addSuccess', test)) - - def addUnexpectedSuccess(self, test, details=None): - self._was_successful = False - if details is not None: - self._events.append(('addUnexpectedSuccess', test, details)) - else: - self._events.append(('addUnexpectedSuccess', test)) - - def progress(self, offset, whence): - self._events.append(('progress', offset, whence)) - - def startTestRun(self): - super(ExtendedTestResult, self).startTestRun() - self._was_successful = True - self._tags = TagContext() - - def startTest(self, test): - super(ExtendedTestResult, self).startTest(test) - self._tags = TagContext(self._tags) - - def stopTest(self, test): - self._tags = self._tags.parent - super(ExtendedTestResult, self).stopTest(test) - - @property - def current_tags(self): - return self._tags.get_current_tags() - - def tags(self, new_tags, gone_tags): - self._tags.change_tags(new_tags, gone_tags) - self._events.append(('tags', new_tags, gone_tags)) - - def time(self, time): - self._events.append(('time', time)) - - def wasSuccessful(self): - return self._was_successful diff --git a/lib/testtools/testtools/testresult/real.py b/lib/testtools/testtools/testresult/real.py deleted file mode 100644 index 0a69872887a..00000000000 --- a/lib/testtools/testtools/testresult/real.py +++ /dev/null @@ -1,981 +0,0 @@ -# Copyright (c) 2008-2012 testtools developers. See LICENSE for details. - -"""Test results and related things.""" - -__metaclass__ = type -__all__ = [ - 'ExtendedToOriginalDecorator', - 'MultiTestResult', - 'Tagger', - 'TestResult', - 'TestResultDecorator', - 'ThreadsafeForwardingResult', - ] - -import datetime -import sys -import unittest - -from testtools.compat import all, str_is_unicode, _u -from testtools.content import ( - text_content, - TracebackContent, - ) -from testtools.helpers import safe_hasattr -from testtools.tags import TagContext - -# From http://docs.python.org/library/datetime.html -_ZERO = datetime.timedelta(0) - -# A UTC class. - -class UTC(datetime.tzinfo): - """UTC""" - - def utcoffset(self, dt): - return _ZERO - - def tzname(self, dt): - return "UTC" - - def dst(self, dt): - return _ZERO - -utc = UTC() - - -class TestResult(unittest.TestResult): - """Subclass of unittest.TestResult extending the protocol for flexability. - - This test result supports an experimental protocol for providing additional - data to in test outcomes. All the outcome methods take an optional dict - 'details'. If supplied any other detail parameters like 'err' or 'reason' - should not be provided. The details dict is a mapping from names to - MIME content objects (see testtools.content). This permits attaching - tracebacks, log files, or even large objects like databases that were - part of the test fixture. Until this API is accepted into upstream - Python it is considered experimental: it may be replaced at any point - by a newer version more in line with upstream Python. Compatibility would - be aimed for in this case, but may not be possible. - - :ivar skip_reasons: A dict of skip-reasons -> list of tests. See addSkip. - """ - - def __init__(self, failfast=False): - # startTestRun resets all attributes, and older clients don't know to - # call startTestRun, so it is called once here. - # Because subclasses may reasonably not expect this, we call the - # specific version we want to run. - self.failfast = failfast - TestResult.startTestRun(self) - - def addExpectedFailure(self, test, err=None, details=None): - """Called when a test has failed in an expected manner. - - Like with addSuccess and addError, testStopped should still be called. - - :param test: The test that has been skipped. - :param err: The exc_info of the error that was raised. - :return: None - """ - # This is the python 2.7 implementation - self.expectedFailures.append( - (test, self._err_details_to_string(test, err, details))) - - def addError(self, test, err=None, details=None): - """Called when an error has occurred. 'err' is a tuple of values as - returned by sys.exc_info(). - - :param details: Alternative way to supply details about the outcome. - see the class docstring for more information. - """ - self.errors.append((test, - self._err_details_to_string(test, err, details))) - if self.failfast: - self.stop() - - def addFailure(self, test, err=None, details=None): - """Called when an error has occurred. 'err' is a tuple of values as - returned by sys.exc_info(). - - :param details: Alternative way to supply details about the outcome. - see the class docstring for more information. - """ - self.failures.append((test, - self._err_details_to_string(test, err, details))) - if self.failfast: - self.stop() - - def addSkip(self, test, reason=None, details=None): - """Called when a test has been skipped rather than running. - - Like with addSuccess and addError, testStopped should still be called. - - This must be called by the TestCase. 'addError' and 'addFailure' will - not call addSkip, since they have no assumptions about the kind of - errors that a test can raise. - - :param test: The test that has been skipped. - :param reason: The reason for the test being skipped. For instance, - u"pyGL is not available". - :param details: Alternative way to supply details about the outcome. - see the class docstring for more information. - :return: None - """ - if reason is None: - reason = details.get('reason') - if reason is None: - reason = 'No reason given' - else: - reason = reason.as_text() - skip_list = self.skip_reasons.setdefault(reason, []) - skip_list.append(test) - - def addSuccess(self, test, details=None): - """Called when a test succeeded.""" - - def addUnexpectedSuccess(self, test, details=None): - """Called when a test was expected to fail, but succeed.""" - self.unexpectedSuccesses.append(test) - if self.failfast: - self.stop() - - def wasSuccessful(self): - """Has this result been successful so far? - - If there have been any errors, failures or unexpected successes, - return False. Otherwise, return True. - - Note: This differs from standard unittest in that we consider - unexpected successes to be equivalent to failures, rather than - successes. - """ - return not (self.errors or self.failures or self.unexpectedSuccesses) - - def _err_details_to_string(self, test, err=None, details=None): - """Convert an error in exc_info form or a contents dict to a string.""" - if err is not None: - return TracebackContent(err, test).as_text() - return _details_to_str(details, special='traceback') - - def _exc_info_to_unicode(self, err, test): - # Deprecated. Only present because subunit upcalls to it. See - # . - return TracebackContent(err, test).as_text() - - def _now(self): - """Return the current 'test time'. - - If the time() method has not been called, this is equivalent to - datetime.now(), otherwise its the last supplied datestamp given to the - time() method. - """ - if self.__now is None: - return datetime.datetime.now(utc) - else: - return self.__now - - def startTestRun(self): - """Called before a test run starts. - - New in Python 2.7. The testtools version resets the result to a - pristine condition ready for use in another test run. Note that this - is different from Python 2.7's startTestRun, which does nothing. - """ - # failfast is reset by the super __init__, so stash it. - failfast = self.failfast - super(TestResult, self).__init__() - self.skip_reasons = {} - self.__now = None - self._tags = TagContext() - # -- Start: As per python 2.7 -- - self.expectedFailures = [] - self.unexpectedSuccesses = [] - self.failfast = failfast - # -- End: As per python 2.7 -- - - def stopTestRun(self): - """Called after a test run completes - - New in python 2.7 - """ - - def startTest(self, test): - super(TestResult, self).startTest(test) - self._tags = TagContext(self._tags) - - def stopTest(self, test): - self._tags = self._tags.parent - super(TestResult, self).stopTest(test) - - @property - def current_tags(self): - """The currently set tags.""" - return self._tags.get_current_tags() - - def tags(self, new_tags, gone_tags): - """Add and remove tags from the test. - - :param new_tags: A set of tags to be added to the stream. - :param gone_tags: A set of tags to be removed from the stream. - """ - self._tags.change_tags(new_tags, gone_tags) - - def time(self, a_datetime): - """Provide a timestamp to represent the current time. - - This is useful when test activity is time delayed, or happening - concurrently and getting the system time between API calls will not - accurately represent the duration of tests (or the whole run). - - Calling time() sets the datetime used by the TestResult object. - Time is permitted to go backwards when using this call. - - :param a_datetime: A datetime.datetime object with TZ information or - None to reset the TestResult to gathering time from the system. - """ - self.__now = a_datetime - - def done(self): - """Called when the test runner is done. - - deprecated in favour of stopTestRun. - """ - - -class MultiTestResult(TestResult): - """A test result that dispatches to many test results.""" - - def __init__(self, *results): - # Setup _results first, as the base class __init__ assigns to failfast. - self._results = list(map(ExtendedToOriginalDecorator, results)) - super(MultiTestResult, self).__init__() - - def __repr__(self): - return '<%s (%s)>' % ( - self.__class__.__name__, ', '.join(map(repr, self._results))) - - def _dispatch(self, message, *args, **kwargs): - return tuple( - getattr(result, message)(*args, **kwargs) - for result in self._results) - - def _get_failfast(self): - return getattr(self._results[0], 'failfast', False) - def _set_failfast(self, value): - self._dispatch('__setattr__', 'failfast', value) - failfast = property(_get_failfast, _set_failfast) - - def _get_shouldStop(self): - return any(self._dispatch('__getattr__', 'shouldStop')) - def _set_shouldStop(self, value): - # Called because we subclass TestResult. Probably should not do that. - pass - shouldStop = property(_get_shouldStop, _set_shouldStop) - - def startTest(self, test): - super(MultiTestResult, self).startTest(test) - return self._dispatch('startTest', test) - - def stop(self): - return self._dispatch('stop') - - def stopTest(self, test): - super(MultiTestResult, self).stopTest(test) - return self._dispatch('stopTest', test) - - def addError(self, test, error=None, details=None): - return self._dispatch('addError', test, error, details=details) - - def addExpectedFailure(self, test, err=None, details=None): - return self._dispatch( - 'addExpectedFailure', test, err, details=details) - - def addFailure(self, test, err=None, details=None): - return self._dispatch('addFailure', test, err, details=details) - - def addSkip(self, test, reason=None, details=None): - return self._dispatch('addSkip', test, reason, details=details) - - def addSuccess(self, test, details=None): - return self._dispatch('addSuccess', test, details=details) - - def addUnexpectedSuccess(self, test, details=None): - return self._dispatch('addUnexpectedSuccess', test, details=details) - - def startTestRun(self): - super(MultiTestResult, self).startTestRun() - return self._dispatch('startTestRun') - - def stopTestRun(self): - return self._dispatch('stopTestRun') - - def tags(self, new_tags, gone_tags): - super(MultiTestResult, self).tags(new_tags, gone_tags) - return self._dispatch('tags', new_tags, gone_tags) - - def time(self, a_datetime): - return self._dispatch('time', a_datetime) - - def done(self): - return self._dispatch('done') - - def wasSuccessful(self): - """Was this result successful? - - Only returns True if every constituent result was successful. - """ - return all(self._dispatch('wasSuccessful')) - - -class TextTestResult(TestResult): - """A TestResult which outputs activity to a text stream.""" - - def __init__(self, stream, failfast=False): - """Construct a TextTestResult writing to stream.""" - super(TextTestResult, self).__init__(failfast=failfast) - self.stream = stream - self.sep1 = '=' * 70 + '\n' - self.sep2 = '-' * 70 + '\n' - - def _delta_to_float(self, a_timedelta): - return (a_timedelta.days * 86400.0 + a_timedelta.seconds + - a_timedelta.microseconds / 1000000.0) - - def _show_list(self, label, error_list): - for test, output in error_list: - self.stream.write(self.sep1) - self.stream.write("%s: %s\n" % (label, test.id())) - self.stream.write(self.sep2) - self.stream.write(output) - - def startTestRun(self): - super(TextTestResult, self).startTestRun() - self.__start = self._now() - self.stream.write("Tests running...\n") - - def stopTestRun(self): - if self.testsRun != 1: - plural = 's' - else: - plural = '' - stop = self._now() - self._show_list('ERROR', self.errors) - self._show_list('FAIL', self.failures) - for test in self.unexpectedSuccesses: - self.stream.write( - "%sUNEXPECTED SUCCESS: %s\n%s" % ( - self.sep1, test.id(), self.sep2)) - self.stream.write("\nRan %d test%s in %.3fs\n" % - (self.testsRun, plural, - self._delta_to_float(stop - self.__start))) - if self.wasSuccessful(): - self.stream.write("OK\n") - else: - self.stream.write("FAILED (") - details = [] - details.append("failures=%d" % ( - sum(map(len, ( - self.failures, self.errors, self.unexpectedSuccesses))))) - self.stream.write(", ".join(details)) - self.stream.write(")\n") - super(TextTestResult, self).stopTestRun() - - -class ThreadsafeForwardingResult(TestResult): - """A TestResult which ensures the target does not receive mixed up calls. - - Multiple ``ThreadsafeForwardingResults`` can forward to the same target - result, and that target result will only ever receive the complete set of - events for one test at a time. - - This is enforced using a semaphore, which further guarantees that tests - will be sent atomically even if the ``ThreadsafeForwardingResults`` are in - different threads. - - ``ThreadsafeForwardingResult`` is typically used by - ``ConcurrentTestSuite``, which creates one ``ThreadsafeForwardingResult`` - per thread, each of which wraps of the TestResult that - ``ConcurrentTestSuite.run()`` is called with. - - target.startTestRun() and target.stopTestRun() are called once for each - ThreadsafeForwardingResult that forwards to the same target. If the target - takes special action on these events, it should take care to accommodate - this. - - time() and tags() calls are batched to be adjacent to the test result and - in the case of tags() are coerced into test-local scope, avoiding the - opportunity for bugs around global state in the target. - """ - - def __init__(self, target, semaphore): - """Create a ThreadsafeForwardingResult forwarding to target. - - :param target: A ``TestResult``. - :param semaphore: A ``threading.Semaphore`` with limit 1. - """ - TestResult.__init__(self) - self.result = ExtendedToOriginalDecorator(target) - self.semaphore = semaphore - self._test_start = None - self._global_tags = set(), set() - self._test_tags = set(), set() - - def __repr__(self): - return '<%s %r>' % (self.__class__.__name__, self.result) - - def _any_tags(self, tags): - return bool(tags[0] or tags[1]) - - def _add_result_with_semaphore(self, method, test, *args, **kwargs): - now = self._now() - self.semaphore.acquire() - try: - self.result.time(self._test_start) - self.result.startTest(test) - self.result.time(now) - if self._any_tags(self._global_tags): - self.result.tags(*self._global_tags) - if self._any_tags(self._test_tags): - self.result.tags(*self._test_tags) - self._test_tags = set(), set() - try: - method(test, *args, **kwargs) - finally: - self.result.stopTest(test) - finally: - self.semaphore.release() - self._test_start = None - - def addError(self, test, err=None, details=None): - self._add_result_with_semaphore(self.result.addError, - test, err, details=details) - - def addExpectedFailure(self, test, err=None, details=None): - self._add_result_with_semaphore(self.result.addExpectedFailure, - test, err, details=details) - - def addFailure(self, test, err=None, details=None): - self._add_result_with_semaphore(self.result.addFailure, - test, err, details=details) - - def addSkip(self, test, reason=None, details=None): - self._add_result_with_semaphore(self.result.addSkip, - test, reason, details=details) - - def addSuccess(self, test, details=None): - self._add_result_with_semaphore(self.result.addSuccess, - test, details=details) - - def addUnexpectedSuccess(self, test, details=None): - self._add_result_with_semaphore(self.result.addUnexpectedSuccess, - test, details=details) - - def progress(self, offset, whence): - pass - - def startTestRun(self): - super(ThreadsafeForwardingResult, self).startTestRun() - self.semaphore.acquire() - try: - self.result.startTestRun() - finally: - self.semaphore.release() - - def _get_shouldStop(self): - self.semaphore.acquire() - try: - return self.result.shouldStop - finally: - self.semaphore.release() - def _set_shouldStop(self, value): - # Another case where we should not subclass TestResult - pass - shouldStop = property(_get_shouldStop, _set_shouldStop) - - def stop(self): - self.semaphore.acquire() - try: - self.result.stop() - finally: - self.semaphore.release() - - def stopTestRun(self): - self.semaphore.acquire() - try: - self.result.stopTestRun() - finally: - self.semaphore.release() - - def done(self): - self.semaphore.acquire() - try: - self.result.done() - finally: - self.semaphore.release() - - def startTest(self, test): - self._test_start = self._now() - super(ThreadsafeForwardingResult, self).startTest(test) - - def wasSuccessful(self): - return self.result.wasSuccessful() - - def tags(self, new_tags, gone_tags): - """See `TestResult`.""" - super(ThreadsafeForwardingResult, self).tags(new_tags, gone_tags) - if self._test_start is not None: - self._test_tags = _merge_tags( - self._test_tags, (new_tags, gone_tags)) - else: - self._global_tags = _merge_tags( - self._global_tags, (new_tags, gone_tags)) - - -def _merge_tags(existing, changed): - new_tags, gone_tags = changed - result_new = set(existing[0]) - result_gone = set(existing[1]) - result_new.update(new_tags) - result_new.difference_update(gone_tags) - result_gone.update(gone_tags) - result_gone.difference_update(new_tags) - return result_new, result_gone - - -class ExtendedToOriginalDecorator(object): - """Permit new TestResult API code to degrade gracefully with old results. - - This decorates an existing TestResult and converts missing outcomes - such as addSkip to older outcomes such as addSuccess. It also supports - the extended details protocol. In all cases the most recent protocol - is attempted first, and fallbacks only occur when the decorated result - does not support the newer style of calling. - """ - - def __init__(self, decorated): - self.decorated = decorated - self._tags = TagContext() - # Only used for old TestResults that do not have failfast. - self._failfast = False - - def __repr__(self): - return '<%s %r>' % (self.__class__.__name__, self.decorated) - - def __getattr__(self, name): - return getattr(self.decorated, name) - - def addError(self, test, err=None, details=None): - try: - self._check_args(err, details) - if details is not None: - try: - return self.decorated.addError(test, details=details) - except TypeError: - # have to convert - err = self._details_to_exc_info(details) - return self.decorated.addError(test, err) - finally: - if self.failfast: - self.stop() - - def addExpectedFailure(self, test, err=None, details=None): - self._check_args(err, details) - addExpectedFailure = getattr( - self.decorated, 'addExpectedFailure', None) - if addExpectedFailure is None: - return self.addSuccess(test) - if details is not None: - try: - return addExpectedFailure(test, details=details) - except TypeError: - # have to convert - err = self._details_to_exc_info(details) - return addExpectedFailure(test, err) - - def addFailure(self, test, err=None, details=None): - try: - self._check_args(err, details) - if details is not None: - try: - return self.decorated.addFailure(test, details=details) - except TypeError: - # have to convert - err = self._details_to_exc_info(details) - return self.decorated.addFailure(test, err) - finally: - if self.failfast: - self.stop() - - def addSkip(self, test, reason=None, details=None): - self._check_args(reason, details) - addSkip = getattr(self.decorated, 'addSkip', None) - if addSkip is None: - return self.decorated.addSuccess(test) - if details is not None: - try: - return addSkip(test, details=details) - except TypeError: - # extract the reason if it's available - try: - reason = details['reason'].as_text() - except KeyError: - reason = _details_to_str(details) - return addSkip(test, reason) - - def addUnexpectedSuccess(self, test, details=None): - try: - outcome = getattr(self.decorated, 'addUnexpectedSuccess', None) - if outcome is None: - try: - test.fail("") - except test.failureException: - return self.addFailure(test, sys.exc_info()) - if details is not None: - try: - return outcome(test, details=details) - except TypeError: - pass - return outcome(test) - finally: - if self.failfast: - self.stop() - - def addSuccess(self, test, details=None): - if details is not None: - try: - return self.decorated.addSuccess(test, details=details) - except TypeError: - pass - return self.decorated.addSuccess(test) - - def _check_args(self, err, details): - param_count = 0 - if err is not None: - param_count += 1 - if details is not None: - param_count += 1 - if param_count != 1: - raise ValueError("Must pass only one of err '%s' and details '%s" - % (err, details)) - - def _details_to_exc_info(self, details): - """Convert a details dict to an exc_info tuple.""" - return ( - _StringException, - _StringException(_details_to_str(details, special='traceback')), - None) - - @property - def current_tags(self): - return getattr( - self.decorated, 'current_tags', self._tags.get_current_tags()) - - def done(self): - try: - return self.decorated.done() - except AttributeError: - return - - def _get_failfast(self): - return getattr(self.decorated, 'failfast', self._failfast) - def _set_failfast(self, value): - if safe_hasattr(self.decorated, 'failfast'): - self.decorated.failfast = value - else: - self._failfast = value - failfast = property(_get_failfast, _set_failfast) - - def progress(self, offset, whence): - method = getattr(self.decorated, 'progress', None) - if method is None: - return - return method(offset, whence) - - @property - def shouldStop(self): - return self.decorated.shouldStop - - def startTest(self, test): - self._tags = TagContext(self._tags) - return self.decorated.startTest(test) - - def startTestRun(self): - self._tags = TagContext() - try: - return self.decorated.startTestRun() - except AttributeError: - return - - def stop(self): - return self.decorated.stop() - - def stopTest(self, test): - self._tags = self._tags.parent - return self.decorated.stopTest(test) - - def stopTestRun(self): - try: - return self.decorated.stopTestRun() - except AttributeError: - return - - def tags(self, new_tags, gone_tags): - method = getattr(self.decorated, 'tags', None) - if method is not None: - return method(new_tags, gone_tags) - else: - self._tags.change_tags(new_tags, gone_tags) - - def time(self, a_datetime): - method = getattr(self.decorated, 'time', None) - if method is None: - return - return method(a_datetime) - - def wasSuccessful(self): - return self.decorated.wasSuccessful() - - -class TestResultDecorator(object): - """General pass-through decorator. - - This provides a base that other TestResults can inherit from to - gain basic forwarding functionality. - """ - - def __init__(self, decorated): - """Create a TestResultDecorator forwarding to decorated.""" - self.decorated = decorated - - def startTest(self, test): - return self.decorated.startTest(test) - - def startTestRun(self): - return self.decorated.startTestRun() - - def stopTest(self, test): - return self.decorated.stopTest(test) - - def stopTestRun(self): - return self.decorated.stopTestRun() - - def addError(self, test, err=None, details=None): - return self.decorated.addError(test, err, details=details) - - def addFailure(self, test, err=None, details=None): - return self.decorated.addFailure(test, err, details=details) - - def addSuccess(self, test, details=None): - return self.decorated.addSuccess(test, details=details) - - def addSkip(self, test, reason=None, details=None): - return self.decorated.addSkip(test, reason, details=details) - - def addExpectedFailure(self, test, err=None, details=None): - return self.decorated.addExpectedFailure(test, err, details=details) - - def addUnexpectedSuccess(self, test, details=None): - return self.decorated.addUnexpectedSuccess(test, details=details) - - def progress(self, offset, whence): - return self.decorated.progress(offset, whence) - - def wasSuccessful(self): - return self.decorated.wasSuccessful() - - @property - def current_tags(self): - return self.decorated.current_tags - - @property - def shouldStop(self): - return self.decorated.shouldStop - - def stop(self): - return self.decorated.stop() - - @property - def testsRun(self): - return self.decorated.testsRun - - def tags(self, new_tags, gone_tags): - return self.decorated.tags(new_tags, gone_tags) - - def time(self, a_datetime): - return self.decorated.time(a_datetime) - - -class Tagger(TestResultDecorator): - """Tag each test individually.""" - - def __init__(self, decorated, new_tags, gone_tags): - """Wrap 'decorated' such that each test is tagged. - - :param new_tags: Tags to be added for each test. - :param gone_tags: Tags to be removed for each test. - """ - super(Tagger, self).__init__(decorated) - self._new_tags = set(new_tags) - self._gone_tags = set(gone_tags) - - def startTest(self, test): - super(Tagger, self).startTest(test) - self.tags(self._new_tags, self._gone_tags) - - -class TestByTestResult(TestResult): - """Call something every time a test completes.""" - - def __init__(self, on_test): - """Construct a ``TestByTestResult``. - - :param on_test: A callable that take a test case, a status (one of - "success", "failure", "error", "skip", or "xfail"), a start time - (a ``datetime`` with timezone), a stop time, an iterable of tags, - and a details dict. Is called at the end of each test (i.e. on - ``stopTest``) with the accumulated values for that test. - """ - super(TestByTestResult, self).__init__() - self._on_test = on_test - - def startTest(self, test): - super(TestByTestResult, self).startTest(test) - self._start_time = self._now() - # There's no supported (i.e. tested) behaviour that relies on these - # being set, but it makes me more comfortable all the same. -- jml - self._status = None - self._details = None - self._stop_time = None - - def stopTest(self, test): - self._stop_time = self._now() - tags = set(self.current_tags) - super(TestByTestResult, self).stopTest(test) - self._on_test( - test=test, - status=self._status, - start_time=self._start_time, - stop_time=self._stop_time, - tags=tags, - details=self._details) - - def _err_to_details(self, test, err, details): - if details: - return details - return {'traceback': TracebackContent(err, test)} - - def addSuccess(self, test, details=None): - super(TestByTestResult, self).addSuccess(test) - self._status = 'success' - self._details = details - - def addFailure(self, test, err=None, details=None): - super(TestByTestResult, self).addFailure(test, err, details) - self._status = 'failure' - self._details = self._err_to_details(test, err, details) - - def addError(self, test, err=None, details=None): - super(TestByTestResult, self).addError(test, err, details) - self._status = 'error' - self._details = self._err_to_details(test, err, details) - - def addSkip(self, test, reason=None, details=None): - super(TestByTestResult, self).addSkip(test, reason, details) - self._status = 'skip' - if details is None: - details = {'reason': text_content(reason)} - elif reason: - # XXX: What if details already has 'reason' key? - details['reason'] = text_content(reason) - self._details = details - - def addExpectedFailure(self, test, err=None, details=None): - super(TestByTestResult, self).addExpectedFailure(test, err, details) - self._status = 'xfail' - self._details = self._err_to_details(test, err, details) - - def addUnexpectedSuccess(self, test, details=None): - super(TestByTestResult, self).addUnexpectedSuccess(test, details) - self._status = 'success' - self._details = details - - -class _StringException(Exception): - """An exception made from an arbitrary string.""" - - if not str_is_unicode: - def __init__(self, string): - if type(string) is not unicode: - raise TypeError("_StringException expects unicode, got %r" % - (string,)) - Exception.__init__(self, string) - - def __str__(self): - return self.args[0].encode("utf-8") - - def __unicode__(self): - return self.args[0] - # For 3.0 and above the default __str__ is fine, so we don't define one. - - def __hash__(self): - return id(self) - - def __eq__(self, other): - try: - return self.args == other.args - except AttributeError: - return False - - -def _format_text_attachment(name, text): - if '\n' in text: - return "%s: {{{\n%s\n}}}\n" % (name, text) - return "%s: {{{%s}}}" % (name, text) - - -def _details_to_str(details, special=None): - """Convert a details dict to a string. - - :param details: A dictionary mapping short names to ``Content`` objects. - :param special: If specified, an attachment that should have special - attention drawn to it. The primary attachment. Normally it's the - traceback that caused the test to fail. - :return: A formatted string that can be included in text test results. - """ - empty_attachments = [] - binary_attachments = [] - text_attachments = [] - special_content = None - # sorted is for testing, may want to remove that and use a dict - # subclass with defined order for items instead. - for key, content in sorted(details.items()): - if content.content_type.type != 'text': - binary_attachments.append((key, content.content_type)) - continue - text = content.as_text().strip() - if not text: - empty_attachments.append(key) - continue - # We want the 'special' attachment to be at the bottom. - if key == special: - special_content = '%s\n' % (text,) - continue - text_attachments.append(_format_text_attachment(key, text)) - if text_attachments and not text_attachments[-1].endswith('\n'): - text_attachments.append('') - if special_content: - text_attachments.append(special_content) - lines = [] - if binary_attachments: - lines.append('Binary content:\n') - for name, content_type in binary_attachments: - lines.append(' %s (%s)\n' % (name, content_type)) - if empty_attachments: - lines.append('Empty attachments:\n') - for name in empty_attachments: - lines.append(' %s\n' % (name,)) - if (binary_attachments or empty_attachments) and text_attachments: - lines.append('\n') - lines.append('\n'.join(text_attachments)) - return _u('').join(lines) diff --git a/lib/testtools/testtools/tests/__init__.py b/lib/testtools/testtools/tests/__init__.py deleted file mode 100644 index df9d44b26d9..00000000000 --- a/lib/testtools/testtools/tests/__init__.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Tests for testtools itself.""" - -# See README for copyright and licensing details. - -from unittest import TestSuite - - -def test_suite(): - from testtools.tests import ( - matchers, - test_compat, - test_content, - test_content_type, - test_deferredruntest, - test_distutilscmd, - test_fixturesupport, - test_helpers, - test_monkey, - test_run, - test_runtest, - test_spinner, - test_tags, - test_testcase, - test_testresult, - test_testsuite, - ) - modules = [ - matchers, - test_compat, - test_content, - test_content_type, - test_deferredruntest, - test_distutilscmd, - test_fixturesupport, - test_helpers, - test_monkey, - test_run, - test_runtest, - test_spinner, - test_tags, - test_testcase, - test_testresult, - test_testsuite, - ] - suites = map(lambda x: x.test_suite(), modules) - return TestSuite(suites) diff --git a/lib/testtools/testtools/tests/helpers.py b/lib/testtools/testtools/tests/helpers.py deleted file mode 100644 index ade2d962f6c..00000000000 --- a/lib/testtools/testtools/tests/helpers.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright (c) 2008-2012 testtools developers. See LICENSE for details. - -"""Helpers for tests.""" - -__all__ = [ - 'LoggingResult', - ] - -import sys - -from testtools import TestResult -from testtools.helpers import ( - safe_hasattr, - ) -from testtools.content import TracebackContent -from testtools import runtest - - -# Importing to preserve compatibility. -safe_hasattr - -# GZ 2010-08-12: Don't do this, pointlessly creates an exc_info cycle -try: - raise Exception -except Exception: - an_exc_info = sys.exc_info() - -# Deprecated: This classes attributes are somewhat non deterministic which -# leads to hard to predict tests (because Python upstream are changing things. -class LoggingResult(TestResult): - """TestResult that logs its event to a list.""" - - def __init__(self, log): - self._events = log - super(LoggingResult, self).__init__() - - def startTest(self, test): - self._events.append(('startTest', test)) - super(LoggingResult, self).startTest(test) - - def stop(self): - self._events.append('stop') - super(LoggingResult, self).stop() - - def stopTest(self, test): - self._events.append(('stopTest', test)) - super(LoggingResult, self).stopTest(test) - - def addFailure(self, test, error): - self._events.append(('addFailure', test, error)) - super(LoggingResult, self).addFailure(test, error) - - def addError(self, test, error): - self._events.append(('addError', test, error)) - super(LoggingResult, self).addError(test, error) - - def addSkip(self, test, reason): - self._events.append(('addSkip', test, reason)) - super(LoggingResult, self).addSkip(test, reason) - - def addSuccess(self, test): - self._events.append(('addSuccess', test)) - super(LoggingResult, self).addSuccess(test) - - def startTestRun(self): - self._events.append('startTestRun') - super(LoggingResult, self).startTestRun() - - def stopTestRun(self): - self._events.append('stopTestRun') - super(LoggingResult, self).stopTestRun() - - def done(self): - self._events.append('done') - super(LoggingResult, self).done() - - def tags(self, new_tags, gone_tags): - self._events.append(('tags', new_tags, gone_tags)) - super(LoggingResult, self).tags(new_tags, gone_tags) - - def time(self, a_datetime): - self._events.append(('time', a_datetime)) - super(LoggingResult, self).time(a_datetime) - - -def is_stack_hidden(): - return TracebackContent.HIDE_INTERNAL_STACK - - -def hide_testtools_stack(should_hide=True): - result = TracebackContent.HIDE_INTERNAL_STACK - TracebackContent.HIDE_INTERNAL_STACK = should_hide - return result - - -def run_with_stack_hidden(should_hide, f, *args, **kwargs): - old_should_hide = hide_testtools_stack(should_hide) - try: - return f(*args, **kwargs) - finally: - hide_testtools_stack(old_should_hide) - - -class FullStackRunTest(runtest.RunTest): - - def _run_user(self, fn, *args, **kwargs): - return run_with_stack_hidden( - False, - super(FullStackRunTest, self)._run_user, fn, *args, **kwargs) diff --git a/lib/testtools/testtools/tests/matchers/__init__.py b/lib/testtools/testtools/tests/matchers/__init__.py deleted file mode 100644 index ebab308e77c..00000000000 --- a/lib/testtools/testtools/tests/matchers/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2009-2012 testtools developers. See LICENSE for details. - - -from unittest import TestSuite - - -def test_suite(): - from testtools.tests.matchers import ( - test_basic, - test_datastructures, - test_dict, - test_doctest, - test_exception, - test_filesystem, - test_higherorder, - test_impl, - ) - modules = [ - test_basic, - test_datastructures, - test_dict, - test_doctest, - test_exception, - test_filesystem, - test_higherorder, - test_impl, - ] - suites = map(lambda x: x.test_suite(), modules) - return TestSuite(suites) diff --git a/lib/testtools/testtools/tests/matchers/helpers.py b/lib/testtools/testtools/tests/matchers/helpers.py deleted file mode 100644 index 3ff87278dae..00000000000 --- a/lib/testtools/testtools/tests/matchers/helpers.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (c) 2008-2012 testtools developers. See LICENSE for details. - -from testtools.tests.helpers import FullStackRunTest - - -class TestMatchersInterface(object): - - run_tests_with = FullStackRunTest - - def test_matches_match(self): - matcher = self.matches_matcher - matches = self.matches_matches - mismatches = self.matches_mismatches - for candidate in matches: - self.assertEqual(None, matcher.match(candidate)) - for candidate in mismatches: - mismatch = matcher.match(candidate) - self.assertNotEqual(None, mismatch) - self.assertNotEqual(None, getattr(mismatch, 'describe', None)) - - def test__str__(self): - # [(expected, object to __str__)]. - from testtools.matchers._doctest import DocTestMatches - examples = self.str_examples - for expected, matcher in examples: - self.assertThat(matcher, DocTestMatches(expected)) - - def test_describe_difference(self): - # [(expected, matchee, matcher), ...] - examples = self.describe_examples - for difference, matchee, matcher in examples: - mismatch = matcher.match(matchee) - self.assertEqual(difference, mismatch.describe()) - - def test_mismatch_details(self): - # The mismatch object must provide get_details, which must return a - # dictionary mapping names to Content objects. - examples = self.describe_examples - for difference, matchee, matcher in examples: - mismatch = matcher.match(matchee) - details = mismatch.get_details() - self.assertEqual(dict(details), details) diff --git a/lib/testtools/testtools/tests/matchers/test_basic.py b/lib/testtools/testtools/tests/matchers/test_basic.py deleted file mode 100644 index 1109fa4bb61..00000000000 --- a/lib/testtools/testtools/tests/matchers/test_basic.py +++ /dev/null @@ -1,374 +0,0 @@ -# Copyright (c) 2008-2012 testtools developers. See LICENSE for details. - -import re - -from testtools import TestCase -from testtools.compat import ( - text_repr, - _b, - _u, - ) -from testtools.matchers._basic import ( - _BinaryMismatch, - Contains, - DoesNotEndWith, - DoesNotStartWith, - EndsWith, - Equals, - Is, - IsInstance, - LessThan, - GreaterThan, - MatchesRegex, - NotEquals, - SameMembers, - StartsWith, - ) -from testtools.tests.helpers import FullStackRunTest -from testtools.tests.matchers.helpers import TestMatchersInterface - - -class Test_BinaryMismatch(TestCase): - """Mismatches from binary comparisons need useful describe output""" - - _long_string = "This is a longish multiline non-ascii string\n\xa7" - _long_b = _b(_long_string) - _long_u = _u(_long_string) - - def test_short_objects(self): - o1, o2 = object(), object() - mismatch = _BinaryMismatch(o1, "!~", o2) - self.assertEqual(mismatch.describe(), "%r !~ %r" % (o1, o2)) - - def test_short_mixed_strings(self): - b, u = _b("\xa7"), _u("\xa7") - mismatch = _BinaryMismatch(b, "!~", u) - self.assertEqual(mismatch.describe(), "%r !~ %r" % (b, u)) - - def test_long_bytes(self): - one_line_b = self._long_b.replace(_b("\n"), _b(" ")) - mismatch = _BinaryMismatch(one_line_b, "!~", self._long_b) - self.assertEqual(mismatch.describe(), - "%s:\nreference = %s\nactual = %s\n" % ("!~", - text_repr(one_line_b), - text_repr(self._long_b, multiline=True))) - - def test_long_unicode(self): - one_line_u = self._long_u.replace("\n", " ") - mismatch = _BinaryMismatch(one_line_u, "!~", self._long_u) - self.assertEqual(mismatch.describe(), - "%s:\nreference = %s\nactual = %s\n" % ("!~", - text_repr(one_line_u), - text_repr(self._long_u, multiline=True))) - - def test_long_mixed_strings(self): - mismatch = _BinaryMismatch(self._long_b, "!~", self._long_u) - self.assertEqual(mismatch.describe(), - "%s:\nreference = %s\nactual = %s\n" % ("!~", - text_repr(self._long_b, multiline=True), - text_repr(self._long_u, multiline=True))) - - def test_long_bytes_and_object(self): - obj = object() - mismatch = _BinaryMismatch(self._long_b, "!~", obj) - self.assertEqual(mismatch.describe(), - "%s:\nreference = %s\nactual = %s\n" % ("!~", - text_repr(self._long_b, multiline=True), - repr(obj))) - - def test_long_unicode_and_object(self): - obj = object() - mismatch = _BinaryMismatch(self._long_u, "!~", obj) - self.assertEqual(mismatch.describe(), - "%s:\nreference = %s\nactual = %s\n" % ("!~", - text_repr(self._long_u, multiline=True), - repr(obj))) - - -class TestEqualsInterface(TestCase, TestMatchersInterface): - - matches_matcher = Equals(1) - matches_matches = [1] - matches_mismatches = [2] - - str_examples = [("Equals(1)", Equals(1)), ("Equals('1')", Equals('1'))] - - describe_examples = [("1 != 2", 2, Equals(1))] - - -class TestNotEqualsInterface(TestCase, TestMatchersInterface): - - matches_matcher = NotEquals(1) - matches_matches = [2] - matches_mismatches = [1] - - str_examples = [ - ("NotEquals(1)", NotEquals(1)), ("NotEquals('1')", NotEquals('1'))] - - describe_examples = [("1 == 1", 1, NotEquals(1))] - - -class TestIsInterface(TestCase, TestMatchersInterface): - - foo = object() - bar = object() - - matches_matcher = Is(foo) - matches_matches = [foo] - matches_mismatches = [bar, 1] - - str_examples = [("Is(2)", Is(2))] - - describe_examples = [("1 is not 2", 2, Is(1))] - - -class TestIsInstanceInterface(TestCase, TestMatchersInterface): - - class Foo:pass - - matches_matcher = IsInstance(Foo) - matches_matches = [Foo()] - matches_mismatches = [object(), 1, Foo] - - str_examples = [ - ("IsInstance(str)", IsInstance(str)), - ("IsInstance(str, int)", IsInstance(str, int)), - ] - - describe_examples = [ - ("'foo' is not an instance of int", 'foo', IsInstance(int)), - ("'foo' is not an instance of any of (int, type)", 'foo', - IsInstance(int, type)), - ] - - -class TestLessThanInterface(TestCase, TestMatchersInterface): - - matches_matcher = LessThan(4) - matches_matches = [-5, 3] - matches_mismatches = [4, 5, 5000] - - str_examples = [ - ("LessThan(12)", LessThan(12)), - ] - - describe_examples = [ - ('4 is not > 5', 5, LessThan(4)), - ('4 is not > 4', 4, LessThan(4)), - ] - - -class TestGreaterThanInterface(TestCase, TestMatchersInterface): - - matches_matcher = GreaterThan(4) - matches_matches = [5, 8] - matches_mismatches = [-2, 0, 4] - - str_examples = [ - ("GreaterThan(12)", GreaterThan(12)), - ] - - describe_examples = [ - ('5 is not < 4', 4, GreaterThan(5)), - ('4 is not < 4', 4, GreaterThan(4)), - ] - - -class TestContainsInterface(TestCase, TestMatchersInterface): - - matches_matcher = Contains('foo') - matches_matches = ['foo', 'afoo', 'fooa'] - matches_mismatches = ['f', 'fo', 'oo', 'faoo', 'foao'] - - str_examples = [ - ("Contains(1)", Contains(1)), - ("Contains('foo')", Contains('foo')), - ] - - describe_examples = [("1 not in 2", 2, Contains(1))] - - -class DoesNotStartWithTests(TestCase): - - run_tests_with = FullStackRunTest - - def test_describe(self): - mismatch = DoesNotStartWith("fo", "bo") - self.assertEqual("'fo' does not start with 'bo'.", mismatch.describe()) - - def test_describe_non_ascii_unicode(self): - string = _u("A\xA7") - suffix = _u("B\xA7") - mismatch = DoesNotStartWith(string, suffix) - self.assertEqual("%s does not start with %s." % ( - text_repr(string), text_repr(suffix)), - mismatch.describe()) - - def test_describe_non_ascii_bytes(self): - string = _b("A\xA7") - suffix = _b("B\xA7") - mismatch = DoesNotStartWith(string, suffix) - self.assertEqual("%r does not start with %r." % (string, suffix), - mismatch.describe()) - - -class StartsWithTests(TestCase): - - run_tests_with = FullStackRunTest - - def test_str(self): - matcher = StartsWith("bar") - self.assertEqual("StartsWith('bar')", str(matcher)) - - def test_str_with_bytes(self): - b = _b("\xA7") - matcher = StartsWith(b) - self.assertEqual("StartsWith(%r)" % (b,), str(matcher)) - - def test_str_with_unicode(self): - u = _u("\xA7") - matcher = StartsWith(u) - self.assertEqual("StartsWith(%r)" % (u,), str(matcher)) - - def test_match(self): - matcher = StartsWith("bar") - self.assertIs(None, matcher.match("barf")) - - def test_mismatch_returns_does_not_start_with(self): - matcher = StartsWith("bar") - self.assertIsInstance(matcher.match("foo"), DoesNotStartWith) - - def test_mismatch_sets_matchee(self): - matcher = StartsWith("bar") - mismatch = matcher.match("foo") - self.assertEqual("foo", mismatch.matchee) - - def test_mismatch_sets_expected(self): - matcher = StartsWith("bar") - mismatch = matcher.match("foo") - self.assertEqual("bar", mismatch.expected) - - -class DoesNotEndWithTests(TestCase): - - run_tests_with = FullStackRunTest - - def test_describe(self): - mismatch = DoesNotEndWith("fo", "bo") - self.assertEqual("'fo' does not end with 'bo'.", mismatch.describe()) - - def test_describe_non_ascii_unicode(self): - string = _u("A\xA7") - suffix = _u("B\xA7") - mismatch = DoesNotEndWith(string, suffix) - self.assertEqual("%s does not end with %s." % ( - text_repr(string), text_repr(suffix)), - mismatch.describe()) - - def test_describe_non_ascii_bytes(self): - string = _b("A\xA7") - suffix = _b("B\xA7") - mismatch = DoesNotEndWith(string, suffix) - self.assertEqual("%r does not end with %r." % (string, suffix), - mismatch.describe()) - - -class EndsWithTests(TestCase): - - run_tests_with = FullStackRunTest - - def test_str(self): - matcher = EndsWith("bar") - self.assertEqual("EndsWith('bar')", str(matcher)) - - def test_str_with_bytes(self): - b = _b("\xA7") - matcher = EndsWith(b) - self.assertEqual("EndsWith(%r)" % (b,), str(matcher)) - - def test_str_with_unicode(self): - u = _u("\xA7") - matcher = EndsWith(u) - self.assertEqual("EndsWith(%r)" % (u,), str(matcher)) - - def test_match(self): - matcher = EndsWith("arf") - self.assertIs(None, matcher.match("barf")) - - def test_mismatch_returns_does_not_end_with(self): - matcher = EndsWith("bar") - self.assertIsInstance(matcher.match("foo"), DoesNotEndWith) - - def test_mismatch_sets_matchee(self): - matcher = EndsWith("bar") - mismatch = matcher.match("foo") - self.assertEqual("foo", mismatch.matchee) - - def test_mismatch_sets_expected(self): - matcher = EndsWith("bar") - mismatch = matcher.match("foo") - self.assertEqual("bar", mismatch.expected) - - -class TestSameMembers(TestCase, TestMatchersInterface): - - matches_matcher = SameMembers([1, 1, 2, 3, {'foo': 'bar'}]) - matches_matches = [ - [1, 1, 2, 3, {'foo': 'bar'}], - [3, {'foo': 'bar'}, 1, 2, 1], - [3, 2, 1, {'foo': 'bar'}, 1], - (2, {'foo': 'bar'}, 3, 1, 1), - ] - matches_mismatches = [ - set([1, 2, 3]), - [1, 1, 2, 3, 5], - [1, 2, 3, {'foo': 'bar'}], - 'foo', - ] - - describe_examples = [ - (("elements differ:\n" - "reference = ['apple', 'orange', 'canteloupe', 'watermelon', 'lemon', 'banana']\n" - "actual = ['orange', 'apple', 'banana', 'sparrow', 'lemon', 'canteloupe']\n" - ": \n" - "missing: ['watermelon']\n" - "extra: ['sparrow']" - ), - ['orange', 'apple', 'banana', 'sparrow', 'lemon', 'canteloupe',], - SameMembers( - ['apple', 'orange', 'canteloupe', 'watermelon', - 'lemon', 'banana',])), - ] - - str_examples = [ - ('SameMembers([1, 2, 3])', SameMembers([1, 2, 3])), - ] - - -class TestMatchesRegex(TestCase, TestMatchersInterface): - - matches_matcher = MatchesRegex('a|b') - matches_matches = ['a', 'b'] - matches_mismatches = ['c'] - - str_examples = [ - ("MatchesRegex('a|b')", MatchesRegex('a|b')), - ("MatchesRegex('a|b', re.M)", MatchesRegex('a|b', re.M)), - ("MatchesRegex('a|b', re.I|re.M)", MatchesRegex('a|b', re.I|re.M)), - ("MatchesRegex(%r)" % (_b("\xA7"),), MatchesRegex(_b("\xA7"))), - ("MatchesRegex(%r)" % (_u("\xA7"),), MatchesRegex(_u("\xA7"))), - ] - - describe_examples = [ - ("'c' does not match /a|b/", 'c', MatchesRegex('a|b')), - ("'c' does not match /a\d/", 'c', MatchesRegex(r'a\d')), - ("%r does not match /\\s+\\xa7/" % (_b('c'),), - _b('c'), MatchesRegex(_b("\\s+\xA7"))), - ("%r does not match /\\s+\\xa7/" % (_u('c'),), - _u('c'), MatchesRegex(_u("\\s+\xA7"))), - ] - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/matchers/test_datastructures.py b/lib/testtools/testtools/tests/matchers/test_datastructures.py deleted file mode 100644 index f6d9d8658c8..00000000000 --- a/lib/testtools/testtools/tests/matchers/test_datastructures.py +++ /dev/null @@ -1,209 +0,0 @@ -# Copyright (c) 2008-2012 testtools developers. See LICENSE for details. - -import doctest -import re -import sys - -from testtools import TestCase -from testtools.compat import StringIO -from testtools.matchers import ( - Annotate, - Equals, - LessThan, - MatchesRegex, - NotEquals, - ) -from testtools.matchers._datastructures import ( - ContainsAll, - MatchesListwise, - MatchesStructure, - MatchesSetwise, - ) -from testtools.tests.helpers import FullStackRunTest -from testtools.tests.matchers.helpers import TestMatchersInterface - - -def run_doctest(obj, name): - p = doctest.DocTestParser() - t = p.get_doctest( - obj.__doc__, sys.modules[obj.__module__].__dict__, name, '', 0) - r = doctest.DocTestRunner() - output = StringIO() - r.run(t, out=output.write) - return r.failures, output.getvalue() - - -class TestMatchesListwise(TestCase): - - run_tests_with = FullStackRunTest - - def test_docstring(self): - failure_count, output = run_doctest( - MatchesListwise, "MatchesListwise") - if failure_count: - self.fail("Doctest failed with %s" % output) - - -class TestMatchesStructure(TestCase, TestMatchersInterface): - - class SimpleClass: - def __init__(self, x, y): - self.x = x - self.y = y - - matches_matcher = MatchesStructure(x=Equals(1), y=Equals(2)) - matches_matches = [SimpleClass(1, 2)] - matches_mismatches = [ - SimpleClass(2, 2), - SimpleClass(1, 1), - SimpleClass(3, 3), - ] - - str_examples = [ - ("MatchesStructure(x=Equals(1))", MatchesStructure(x=Equals(1))), - ("MatchesStructure(y=Equals(2))", MatchesStructure(y=Equals(2))), - ("MatchesStructure(x=Equals(1), y=Equals(2))", - MatchesStructure(x=Equals(1), y=Equals(2))), - ] - - describe_examples = [ - ("""\ -Differences: [ -3 != 1: x -]""", SimpleClass(1, 2), MatchesStructure(x=Equals(3), y=Equals(2))), - ("""\ -Differences: [ -3 != 2: y -]""", SimpleClass(1, 2), MatchesStructure(x=Equals(1), y=Equals(3))), - ("""\ -Differences: [ -0 != 1: x -0 != 2: y -]""", SimpleClass(1, 2), MatchesStructure(x=Equals(0), y=Equals(0))), - ] - - def test_fromExample(self): - self.assertThat( - self.SimpleClass(1, 2), - MatchesStructure.fromExample(self.SimpleClass(1, 3), 'x')) - - def test_byEquality(self): - self.assertThat( - self.SimpleClass(1, 2), - MatchesStructure.byEquality(x=1)) - - def test_withStructure(self): - self.assertThat( - self.SimpleClass(1, 2), - MatchesStructure.byMatcher(LessThan, x=2)) - - def test_update(self): - self.assertThat( - self.SimpleClass(1, 2), - MatchesStructure(x=NotEquals(1)).update(x=Equals(1))) - - def test_update_none(self): - self.assertThat( - self.SimpleClass(1, 2), - MatchesStructure(x=Equals(1), z=NotEquals(42)).update( - z=None)) - - -class TestMatchesSetwise(TestCase): - - run_tests_with = FullStackRunTest - - def assertMismatchWithDescriptionMatching(self, value, matcher, - description_matcher): - mismatch = matcher.match(value) - if mismatch is None: - self.fail("%s matched %s" % (matcher, value)) - actual_description = mismatch.describe() - self.assertThat( - actual_description, - Annotate( - "%s matching %s" % (matcher, value), - description_matcher)) - - def test_matches(self): - self.assertIs( - None, MatchesSetwise(Equals(1), Equals(2)).match([2, 1])) - - def test_mismatches(self): - self.assertMismatchWithDescriptionMatching( - [2, 3], MatchesSetwise(Equals(1), Equals(2)), - MatchesRegex('.*There was 1 mismatch$', re.S)) - - def test_too_many_matchers(self): - self.assertMismatchWithDescriptionMatching( - [2, 3], MatchesSetwise(Equals(1), Equals(2), Equals(3)), - Equals('There was 1 matcher left over: Equals(1)')) - - def test_too_many_values(self): - self.assertMismatchWithDescriptionMatching( - [1, 2, 3], MatchesSetwise(Equals(1), Equals(2)), - Equals('There was 1 value left over: [3]')) - - def test_two_too_many_matchers(self): - self.assertMismatchWithDescriptionMatching( - [3], MatchesSetwise(Equals(1), Equals(2), Equals(3)), - MatchesRegex( - 'There were 2 matchers left over: Equals\([12]\), ' - 'Equals\([12]\)')) - - def test_two_too_many_values(self): - self.assertMismatchWithDescriptionMatching( - [1, 2, 3, 4], MatchesSetwise(Equals(1), Equals(2)), - MatchesRegex( - 'There were 2 values left over: \[[34], [34]\]')) - - def test_mismatch_and_too_many_matchers(self): - self.assertMismatchWithDescriptionMatching( - [2, 3], MatchesSetwise(Equals(0), Equals(1), Equals(2)), - MatchesRegex( - '.*There was 1 mismatch and 1 extra matcher: Equals\([01]\)', - re.S)) - - def test_mismatch_and_too_many_values(self): - self.assertMismatchWithDescriptionMatching( - [2, 3, 4], MatchesSetwise(Equals(1), Equals(2)), - MatchesRegex( - '.*There was 1 mismatch and 1 extra value: \[[34]\]', - re.S)) - - def test_mismatch_and_two_too_many_matchers(self): - self.assertMismatchWithDescriptionMatching( - [3, 4], MatchesSetwise( - Equals(0), Equals(1), Equals(2), Equals(3)), - MatchesRegex( - '.*There was 1 mismatch and 2 extra matchers: ' - 'Equals\([012]\), Equals\([012]\)', re.S)) - - def test_mismatch_and_two_too_many_values(self): - self.assertMismatchWithDescriptionMatching( - [2, 3, 4, 5], MatchesSetwise(Equals(1), Equals(2)), - MatchesRegex( - '.*There was 1 mismatch and 2 extra values: \[[145], [145]\]', - re.S)) - - -class TestContainsAllInterface(TestCase, TestMatchersInterface): - - matches_matcher = ContainsAll(['foo', 'bar']) - matches_matches = [['foo', 'bar'], ['foo', 'z', 'bar'], ['bar', 'foo']] - matches_mismatches = [['f', 'g'], ['foo', 'baz'], []] - - str_examples = [( - "MatchesAll(Contains('foo'), Contains('bar'))", - ContainsAll(['foo', 'bar'])), - ] - - describe_examples = [("""Differences: [ -'baz' not in 'foo' -]""", - 'foo', ContainsAll(['foo', 'baz']))] - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/matchers/test_dict.py b/lib/testtools/testtools/tests/matchers/test_dict.py deleted file mode 100644 index c6e2c9c48c2..00000000000 --- a/lib/testtools/testtools/tests/matchers/test_dict.py +++ /dev/null @@ -1,222 +0,0 @@ -from testtools import TestCase -from testtools.matchers import ( - Equals, - NotEquals, - Not, - ) -from testtools.matchers._dict import ( - ContainedByDict, - ContainsDict, - KeysEqual, - MatchesAllDict, - MatchesDict, - _SubDictOf, - ) -from testtools.tests.matchers.helpers import TestMatchersInterface - - -class TestMatchesAllDictInterface(TestCase, TestMatchersInterface): - - matches_matcher = MatchesAllDict({'a': NotEquals(1), 'b': NotEquals(2)}) - matches_matches = [3, 4] - matches_mismatches = [1, 2] - - str_examples = [ - ("MatchesAllDict({'a': NotEquals(1), 'b': NotEquals(2)})", - matches_matcher)] - - describe_examples = [ - ("""a: 1 == 1""", 1, matches_matcher), - ] - - -class TestKeysEqual(TestCase, TestMatchersInterface): - - matches_matcher = KeysEqual('foo', 'bar') - matches_matches = [ - {'foo': 0, 'bar': 1}, - ] - matches_mismatches = [ - {}, - {'foo': 0}, - {'bar': 1}, - {'foo': 0, 'bar': 1, 'baz': 2}, - {'a': None, 'b': None, 'c': None}, - ] - - str_examples = [ - ("KeysEqual('foo', 'bar')", KeysEqual('foo', 'bar')), - ] - - describe_examples = [] - - def test_description(self): - matchee = {'foo': 0, 'bar': 1, 'baz': 2} - mismatch = KeysEqual('foo', 'bar').match(matchee) - description = mismatch.describe() - self.assertThat( - description, Equals( - "['bar', 'foo'] does not match %r: Keys not equal" - % (matchee,))) - - -class TestSubDictOf(TestCase, TestMatchersInterface): - - matches_matcher = _SubDictOf({'foo': 'bar', 'baz': 'qux'}) - - matches_matches = [ - {'foo': 'bar', 'baz': 'qux'}, - {'foo': 'bar'}, - ] - - matches_mismatches = [ - {'foo': 'bar', 'baz': 'qux', 'cat': 'dog'}, - {'foo': 'bar', 'cat': 'dog'}, - ] - - str_examples = [] - describe_examples = [] - - -class TestMatchesDict(TestCase, TestMatchersInterface): - - matches_matcher = MatchesDict( - {'foo': Equals('bar'), 'baz': Not(Equals('qux'))}) - - matches_matches = [ - {'foo': 'bar', 'baz': None}, - {'foo': 'bar', 'baz': 'quux'}, - ] - matches_mismatches = [ - {}, - {'foo': 'bar', 'baz': 'qux'}, - {'foo': 'bop', 'baz': 'qux'}, - {'foo': 'bar', 'baz': 'quux', 'cat': 'dog'}, - {'foo': 'bar', 'cat': 'dog'}, - ] - - str_examples = [ - ("MatchesDict({'baz': %s, 'foo': %s})" % ( - Not(Equals('qux')), Equals('bar')), - matches_matcher), - ] - - describe_examples = [ - ("Missing: {\n" - " 'baz': Not(Equals('qux')),\n" - " 'foo': Equals('bar'),\n" - "}", - {}, matches_matcher), - ("Differences: {\n" - " 'baz': 'qux' matches Equals('qux'),\n" - "}", - {'foo': 'bar', 'baz': 'qux'}, matches_matcher), - ("Differences: {\n" - " 'baz': 'qux' matches Equals('qux'),\n" - " 'foo': 'bar' != 'bop',\n" - "}", - {'foo': 'bop', 'baz': 'qux'}, matches_matcher), - ("Extra: {\n" - " 'cat': 'dog',\n" - "}", - {'foo': 'bar', 'baz': 'quux', 'cat': 'dog'}, matches_matcher), - ("Extra: {\n" - " 'cat': 'dog',\n" - "}\n" - "Missing: {\n" - " 'baz': Not(Equals('qux')),\n" - "}", - {'foo': 'bar', 'cat': 'dog'}, matches_matcher), - ] - - -class TestContainsDict(TestCase, TestMatchersInterface): - - matches_matcher = ContainsDict( - {'foo': Equals('bar'), 'baz': Not(Equals('qux'))}) - - matches_matches = [ - {'foo': 'bar', 'baz': None}, - {'foo': 'bar', 'baz': 'quux'}, - {'foo': 'bar', 'baz': 'quux', 'cat': 'dog'}, - ] - matches_mismatches = [ - {}, - {'foo': 'bar', 'baz': 'qux'}, - {'foo': 'bop', 'baz': 'qux'}, - {'foo': 'bar', 'cat': 'dog'}, - {'foo': 'bar'}, - ] - - str_examples = [ - ("ContainsDict({'baz': %s, 'foo': %s})" % ( - Not(Equals('qux')), Equals('bar')), - matches_matcher), - ] - - describe_examples = [ - ("Missing: {\n" - " 'baz': Not(Equals('qux')),\n" - " 'foo': Equals('bar'),\n" - "}", - {}, matches_matcher), - ("Differences: {\n" - " 'baz': 'qux' matches Equals('qux'),\n" - "}", - {'foo': 'bar', 'baz': 'qux'}, matches_matcher), - ("Differences: {\n" - " 'baz': 'qux' matches Equals('qux'),\n" - " 'foo': 'bar' != 'bop',\n" - "}", - {'foo': 'bop', 'baz': 'qux'}, matches_matcher), - ("Missing: {\n" - " 'baz': Not(Equals('qux')),\n" - "}", - {'foo': 'bar', 'cat': 'dog'}, matches_matcher), - ] - - -class TestContainedByDict(TestCase, TestMatchersInterface): - - matches_matcher = ContainedByDict( - {'foo': Equals('bar'), 'baz': Not(Equals('qux'))}) - - matches_matches = [ - {}, - {'foo': 'bar'}, - {'foo': 'bar', 'baz': 'quux'}, - {'baz': 'quux'}, - ] - matches_mismatches = [ - {'foo': 'bar', 'baz': 'quux', 'cat': 'dog'}, - {'foo': 'bar', 'baz': 'qux'}, - {'foo': 'bop', 'baz': 'qux'}, - {'foo': 'bar', 'cat': 'dog'}, - ] - - str_examples = [ - ("ContainedByDict({'baz': %s, 'foo': %s})" % ( - Not(Equals('qux')), Equals('bar')), - matches_matcher), - ] - - describe_examples = [ - ("Differences: {\n" - " 'baz': 'qux' matches Equals('qux'),\n" - "}", - {'foo': 'bar', 'baz': 'qux'}, matches_matcher), - ("Differences: {\n" - " 'baz': 'qux' matches Equals('qux'),\n" - " 'foo': 'bar' != 'bop',\n" - "}", - {'foo': 'bop', 'baz': 'qux'}, matches_matcher), - ("Extra: {\n" - " 'cat': 'dog',\n" - "}", - {'foo': 'bar', 'cat': 'dog'}, matches_matcher), - ] - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/matchers/test_doctest.py b/lib/testtools/testtools/tests/matchers/test_doctest.py deleted file mode 100644 index 81b9579dbf0..00000000000 --- a/lib/testtools/testtools/tests/matchers/test_doctest.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright (c) 2008-2012 testtools developers. See LICENSE for details. - -import doctest - -from testtools import TestCase -from testtools.compat import ( - str_is_unicode, - _b, - _u, - ) -from testtools.matchers._doctest import DocTestMatches -from testtools.tests.helpers import FullStackRunTest -from testtools.tests.matchers.helpers import TestMatchersInterface - - - -class TestDocTestMatchesInterface(TestCase, TestMatchersInterface): - - matches_matcher = DocTestMatches("Ran 1 test in ...s", doctest.ELLIPSIS) - matches_matches = ["Ran 1 test in 0.000s", "Ran 1 test in 1.234s"] - matches_mismatches = ["Ran 1 tests in 0.000s", "Ran 2 test in 0.000s"] - - str_examples = [("DocTestMatches('Ran 1 test in ...s\\n')", - DocTestMatches("Ran 1 test in ...s")), - ("DocTestMatches('foo\\n', flags=8)", DocTestMatches("foo", flags=8)), - ] - - describe_examples = [('Expected:\n Ran 1 tests in ...s\nGot:\n' - ' Ran 1 test in 0.123s\n', "Ran 1 test in 0.123s", - DocTestMatches("Ran 1 tests in ...s", doctest.ELLIPSIS))] - - -class TestDocTestMatchesInterfaceUnicode(TestCase, TestMatchersInterface): - - matches_matcher = DocTestMatches(_u("\xa7..."), doctest.ELLIPSIS) - matches_matches = [_u("\xa7"), _u("\xa7 more\n")] - matches_mismatches = ["\\xa7", _u("more \xa7"), _u("\n\xa7")] - - str_examples = [("DocTestMatches(%r)" % (_u("\xa7\n"),), - DocTestMatches(_u("\xa7"))), - ] - - describe_examples = [( - _u("Expected:\n \xa7\nGot:\n a\n"), - "a", - DocTestMatches(_u("\xa7"), doctest.ELLIPSIS))] - - -class TestDocTestMatchesSpecific(TestCase): - - run_tests_with = FullStackRunTest - - def test___init__simple(self): - matcher = DocTestMatches("foo") - self.assertEqual("foo\n", matcher.want) - - def test___init__flags(self): - matcher = DocTestMatches("bar\n", doctest.ELLIPSIS) - self.assertEqual("bar\n", matcher.want) - self.assertEqual(doctest.ELLIPSIS, matcher.flags) - - def test_describe_non_ascii_bytes(self): - """Even with bytestrings, the mismatch should be coercible to unicode - - DocTestMatches is intended for text, but the Python 2 str type also - permits arbitrary binary inputs. This is a slightly bogus thing to do, - and under Python 3 using bytes objects will reasonably raise an error. - """ - header = _b("\x89PNG\r\n\x1a\n...") - if str_is_unicode: - self.assertRaises(TypeError, - DocTestMatches, header, doctest.ELLIPSIS) - return - matcher = DocTestMatches(header, doctest.ELLIPSIS) - mismatch = matcher.match(_b("GIF89a\1\0\1\0\0\0\0;")) - # Must be treatable as unicode text, the exact output matters less - self.assertTrue(unicode(mismatch.describe())) - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/matchers/test_exception.py b/lib/testtools/testtools/tests/matchers/test_exception.py deleted file mode 100644 index ef7185f19a4..00000000000 --- a/lib/testtools/testtools/tests/matchers/test_exception.py +++ /dev/null @@ -1,192 +0,0 @@ -# Copyright (c) 2008-2012 testtools developers. See LICENSE for details. - -import sys - -from testtools import TestCase -from testtools.matchers import ( - AfterPreprocessing, - Equals, - ) -from testtools.matchers._exception import ( - MatchesException, - Raises, - raises, - ) -from testtools.tests.helpers import FullStackRunTest -from testtools.tests.matchers.helpers import TestMatchersInterface - - -def make_error(type, *args, **kwargs): - try: - raise type(*args, **kwargs) - except type: - return sys.exc_info() - - -class TestMatchesExceptionInstanceInterface(TestCase, TestMatchersInterface): - - matches_matcher = MatchesException(ValueError("foo")) - error_foo = make_error(ValueError, 'foo') - error_bar = make_error(ValueError, 'bar') - error_base_foo = make_error(Exception, 'foo') - matches_matches = [error_foo] - matches_mismatches = [error_bar, error_base_foo] - - str_examples = [ - ("MatchesException(Exception('foo',))", - MatchesException(Exception('foo'))) - ] - describe_examples = [ - ("%r is not a %r" % (Exception, ValueError), - error_base_foo, - MatchesException(ValueError("foo"))), - ("ValueError('bar',) has different arguments to ValueError('foo',).", - error_bar, - MatchesException(ValueError("foo"))), - ] - - -class TestMatchesExceptionTypeInterface(TestCase, TestMatchersInterface): - - matches_matcher = MatchesException(ValueError) - error_foo = make_error(ValueError, 'foo') - error_sub = make_error(UnicodeError, 'bar') - error_base_foo = make_error(Exception, 'foo') - matches_matches = [error_foo, error_sub] - matches_mismatches = [error_base_foo] - - str_examples = [ - ("MatchesException(%r)" % Exception, - MatchesException(Exception)) - ] - describe_examples = [ - ("%r is not a %r" % (Exception, ValueError), - error_base_foo, - MatchesException(ValueError)), - ] - - -class TestMatchesExceptionTypeReInterface(TestCase, TestMatchersInterface): - - matches_matcher = MatchesException(ValueError, 'fo.') - error_foo = make_error(ValueError, 'foo') - error_sub = make_error(UnicodeError, 'foo') - error_bar = make_error(ValueError, 'bar') - matches_matches = [error_foo, error_sub] - matches_mismatches = [error_bar] - - str_examples = [ - ("MatchesException(%r)" % Exception, - MatchesException(Exception, 'fo.')) - ] - describe_examples = [ - ("'bar' does not match /fo./", - error_bar, MatchesException(ValueError, "fo.")), - ] - - -class TestMatchesExceptionTypeMatcherInterface(TestCase, TestMatchersInterface): - - matches_matcher = MatchesException( - ValueError, AfterPreprocessing(str, Equals('foo'))) - error_foo = make_error(ValueError, 'foo') - error_sub = make_error(UnicodeError, 'foo') - error_bar = make_error(ValueError, 'bar') - matches_matches = [error_foo, error_sub] - matches_mismatches = [error_bar] - - str_examples = [ - ("MatchesException(%r)" % Exception, - MatchesException(Exception, Equals('foo'))) - ] - describe_examples = [ - ("5 != %r" % (error_bar[1],), - error_bar, MatchesException(ValueError, Equals(5))), - ] - - -class TestRaisesInterface(TestCase, TestMatchersInterface): - - matches_matcher = Raises() - def boom(): - raise Exception('foo') - matches_matches = [boom] - matches_mismatches = [lambda:None] - - # Tricky to get function objects to render constantly, and the interfaces - # helper uses assertEqual rather than (for instance) DocTestMatches. - str_examples = [] - - describe_examples = [] - - -class TestRaisesExceptionMatcherInterface(TestCase, TestMatchersInterface): - - matches_matcher = Raises( - exception_matcher=MatchesException(Exception('foo'))) - def boom_bar(): - raise Exception('bar') - def boom_foo(): - raise Exception('foo') - matches_matches = [boom_foo] - matches_mismatches = [lambda:None, boom_bar] - - # Tricky to get function objects to render constantly, and the interfaces - # helper uses assertEqual rather than (for instance) DocTestMatches. - str_examples = [] - - describe_examples = [] - - -class TestRaisesBaseTypes(TestCase): - - run_tests_with = FullStackRunTest - - def raiser(self): - raise KeyboardInterrupt('foo') - - def test_KeyboardInterrupt_matched(self): - # When KeyboardInterrupt is matched, it is swallowed. - matcher = Raises(MatchesException(KeyboardInterrupt)) - self.assertThat(self.raiser, matcher) - - def test_KeyboardInterrupt_propogates(self): - # The default 'it raised' propogates KeyboardInterrupt. - match_keyb = Raises(MatchesException(KeyboardInterrupt)) - def raise_keyb_from_match(): - matcher = Raises() - matcher.match(self.raiser) - self.assertThat(raise_keyb_from_match, match_keyb) - - def test_KeyboardInterrupt_match_Exception_propogates(self): - # If the raised exception isn't matched, and it is not a subclass of - # Exception, it is propogated. - match_keyb = Raises(MatchesException(KeyboardInterrupt)) - def raise_keyb_from_match(): - if sys.version_info > (2, 5): - matcher = Raises(MatchesException(Exception)) - else: - # On Python 2.4 KeyboardInterrupt is a StandardError subclass - # but should propogate from less generic exception matchers - matcher = Raises(MatchesException(EnvironmentError)) - matcher.match(self.raiser) - self.assertThat(raise_keyb_from_match, match_keyb) - - -class TestRaisesConvenience(TestCase): - - run_tests_with = FullStackRunTest - - def test_exc_type(self): - self.assertThat(lambda: 1/0, raises(ZeroDivisionError)) - - def test_exc_value(self): - e = RuntimeError("You lose!") - def raiser(): - raise e - self.assertThat(raiser, raises(e)) - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/matchers/test_filesystem.py b/lib/testtools/testtools/tests/matchers/test_filesystem.py deleted file mode 100644 index 917ff2ed058..00000000000 --- a/lib/testtools/testtools/tests/matchers/test_filesystem.py +++ /dev/null @@ -1,243 +0,0 @@ -# Copyright (c) 2008-2012 testtools developers. See LICENSE for details. - -import os -import shutil -import tarfile -import tempfile - -from testtools import TestCase -from testtools.matchers import ( - Contains, - DocTestMatches, - Equals, - ) -from testtools.matchers._filesystem import ( - DirContains, - DirExists, - FileContains, - FileExists, - HasPermissions, - PathExists, - SamePath, - TarballContains, - ) - - -class PathHelpers(object): - - def mkdtemp(self): - directory = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, directory) - return directory - - def create_file(self, filename, contents=''): - fp = open(filename, 'w') - try: - fp.write(contents) - finally: - fp.close() - - def touch(self, filename): - return self.create_file(filename) - - -class TestPathExists(TestCase, PathHelpers): - - def test_exists(self): - tempdir = self.mkdtemp() - self.assertThat(tempdir, PathExists()) - - def test_not_exists(self): - doesntexist = os.path.join(self.mkdtemp(), 'doesntexist') - mismatch = PathExists().match(doesntexist) - self.assertThat( - "%s does not exist." % doesntexist, Equals(mismatch.describe())) - - -class TestDirExists(TestCase, PathHelpers): - - def test_exists(self): - tempdir = self.mkdtemp() - self.assertThat(tempdir, DirExists()) - - def test_not_exists(self): - doesntexist = os.path.join(self.mkdtemp(), 'doesntexist') - mismatch = DirExists().match(doesntexist) - self.assertThat( - PathExists().match(doesntexist).describe(), - Equals(mismatch.describe())) - - def test_not_a_directory(self): - filename = os.path.join(self.mkdtemp(), 'foo') - self.touch(filename) - mismatch = DirExists().match(filename) - self.assertThat( - "%s is not a directory." % filename, Equals(mismatch.describe())) - - -class TestFileExists(TestCase, PathHelpers): - - def test_exists(self): - tempdir = self.mkdtemp() - filename = os.path.join(tempdir, 'filename') - self.touch(filename) - self.assertThat(filename, FileExists()) - - def test_not_exists(self): - doesntexist = os.path.join(self.mkdtemp(), 'doesntexist') - mismatch = FileExists().match(doesntexist) - self.assertThat( - PathExists().match(doesntexist).describe(), - Equals(mismatch.describe())) - - def test_not_a_file(self): - tempdir = self.mkdtemp() - mismatch = FileExists().match(tempdir) - self.assertThat( - "%s is not a file." % tempdir, Equals(mismatch.describe())) - - -class TestDirContains(TestCase, PathHelpers): - - def test_empty(self): - tempdir = self.mkdtemp() - self.assertThat(tempdir, DirContains([])) - - def test_not_exists(self): - doesntexist = os.path.join(self.mkdtemp(), 'doesntexist') - mismatch = DirContains([]).match(doesntexist) - self.assertThat( - PathExists().match(doesntexist).describe(), - Equals(mismatch.describe())) - - def test_contains_files(self): - tempdir = self.mkdtemp() - self.touch(os.path.join(tempdir, 'foo')) - self.touch(os.path.join(tempdir, 'bar')) - self.assertThat(tempdir, DirContains(['bar', 'foo'])) - - def test_matcher(self): - tempdir = self.mkdtemp() - self.touch(os.path.join(tempdir, 'foo')) - self.touch(os.path.join(tempdir, 'bar')) - self.assertThat(tempdir, DirContains(matcher=Contains('bar'))) - - def test_neither_specified(self): - self.assertRaises(AssertionError, DirContains) - - def test_both_specified(self): - self.assertRaises( - AssertionError, DirContains, filenames=[], matcher=Contains('a')) - - def test_does_not_contain_files(self): - tempdir = self.mkdtemp() - self.touch(os.path.join(tempdir, 'foo')) - mismatch = DirContains(['bar', 'foo']).match(tempdir) - self.assertThat( - Equals(['bar', 'foo']).match(['foo']).describe(), - Equals(mismatch.describe())) - - -class TestFileContains(TestCase, PathHelpers): - - def test_not_exists(self): - doesntexist = os.path.join(self.mkdtemp(), 'doesntexist') - mismatch = FileContains('').match(doesntexist) - self.assertThat( - PathExists().match(doesntexist).describe(), - Equals(mismatch.describe())) - - def test_contains(self): - tempdir = self.mkdtemp() - filename = os.path.join(tempdir, 'foo') - self.create_file(filename, 'Hello World!') - self.assertThat(filename, FileContains('Hello World!')) - - def test_matcher(self): - tempdir = self.mkdtemp() - filename = os.path.join(tempdir, 'foo') - self.create_file(filename, 'Hello World!') - self.assertThat( - filename, FileContains(matcher=DocTestMatches('Hello World!'))) - - def test_neither_specified(self): - self.assertRaises(AssertionError, FileContains) - - def test_both_specified(self): - self.assertRaises( - AssertionError, FileContains, contents=[], matcher=Contains('a')) - - def test_does_not_contain(self): - tempdir = self.mkdtemp() - filename = os.path.join(tempdir, 'foo') - self.create_file(filename, 'Goodbye Cruel World!') - mismatch = FileContains('Hello World!').match(filename) - self.assertThat( - Equals('Hello World!').match('Goodbye Cruel World!').describe(), - Equals(mismatch.describe())) -class TestTarballContains(TestCase, PathHelpers): - - def test_match(self): - tempdir = self.mkdtemp() - in_temp_dir = lambda x: os.path.join(tempdir, x) - self.touch(in_temp_dir('a')) - self.touch(in_temp_dir('b')) - tarball = tarfile.open(in_temp_dir('foo.tar.gz'), 'w') - tarball.add(in_temp_dir('a'), 'a') - tarball.add(in_temp_dir('b'), 'b') - tarball.close() - self.assertThat( - in_temp_dir('foo.tar.gz'), TarballContains(['b', 'a'])) - - def test_mismatch(self): - tempdir = self.mkdtemp() - in_temp_dir = lambda x: os.path.join(tempdir, x) - self.touch(in_temp_dir('a')) - self.touch(in_temp_dir('b')) - tarball = tarfile.open(in_temp_dir('foo.tar.gz'), 'w') - tarball.add(in_temp_dir('a'), 'a') - tarball.add(in_temp_dir('b'), 'b') - tarball.close() - mismatch = TarballContains(['d', 'c']).match(in_temp_dir('foo.tar.gz')) - self.assertEqual( - mismatch.describe(), - Equals(['c', 'd']).match(['a', 'b']).describe()) - - -class TestSamePath(TestCase, PathHelpers): - - def test_same_string(self): - self.assertThat('foo', SamePath('foo')) - - def test_relative_and_absolute(self): - path = 'foo' - abspath = os.path.abspath(path) - self.assertThat(path, SamePath(abspath)) - self.assertThat(abspath, SamePath(path)) - - def test_real_path(self): - tempdir = self.mkdtemp() - source = os.path.join(tempdir, 'source') - self.touch(source) - target = os.path.join(tempdir, 'target') - try: - os.symlink(source, target) - except (AttributeError, NotImplementedError): - self.skip("No symlink support") - self.assertThat(source, SamePath(target)) - self.assertThat(target, SamePath(source)) - - -class TestHasPermissions(TestCase, PathHelpers): - - def test_match(self): - tempdir = self.mkdtemp() - filename = os.path.join(tempdir, 'filename') - self.touch(filename) - permissions = oct(os.stat(filename).st_mode)[-4:] - self.assertThat(filename, HasPermissions(permissions)) - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/matchers/test_higherorder.py b/lib/testtools/testtools/tests/matchers/test_higherorder.py deleted file mode 100644 index c5cc44eb1d4..00000000000 --- a/lib/testtools/testtools/tests/matchers/test_higherorder.py +++ /dev/null @@ -1,227 +0,0 @@ -# Copyright (c) 2008-2011 testtools developers. See LICENSE for details. - -from testtools import TestCase -from testtools.matchers import ( - DocTestMatches, - Equals, - LessThan, - MatchesStructure, - Mismatch, - NotEquals, - ) -from testtools.matchers._higherorder import ( - AfterPreprocessing, - AllMatch, - Annotate, - AnnotatedMismatch, - AnyMatch, - MatchesAny, - MatchesAll, - MatchesPredicate, - Not, - ) -from testtools.tests.helpers import FullStackRunTest -from testtools.tests.matchers.helpers import TestMatchersInterface - - -class TestAllMatch(TestCase, TestMatchersInterface): - - matches_matcher = AllMatch(LessThan(10)) - matches_matches = [ - [9, 9, 9], - (9, 9), - iter([9, 9, 9, 9, 9]), - ] - matches_mismatches = [ - [11, 9, 9], - iter([9, 12, 9, 11]), - ] - - str_examples = [ - ("AllMatch(LessThan(12))", AllMatch(LessThan(12))), - ] - - describe_examples = [ - ('Differences: [\n' - '10 is not > 11\n' - '10 is not > 10\n' - ']', - [11, 9, 10], - AllMatch(LessThan(10))), - ] - - -class TestAnyMatch(TestCase, TestMatchersInterface): - - matches_matcher = AnyMatch(Equals('elephant')) - matches_matches = [ - ['grass', 'cow', 'steak', 'milk', 'elephant'], - (13, 'elephant'), - ['elephant', 'elephant', 'elephant'], - set(['hippo', 'rhino', 'elephant']), - ] - matches_mismatches = [ - [], - ['grass', 'cow', 'steak', 'milk'], - (13, 12, 10), - ['element', 'hephalump', 'pachyderm'], - set(['hippo', 'rhino', 'diplodocus']), - ] - - str_examples = [ - ("AnyMatch(Equals('elephant'))", AnyMatch(Equals('elephant'))), - ] - - describe_examples = [ - ('Differences: [\n' - '7 != 11\n' - '7 != 9\n' - '7 != 10\n' - ']', - [11, 9, 10], - AnyMatch(Equals(7))), - ] - - -class TestAfterPreprocessing(TestCase, TestMatchersInterface): - - def parity(x): - return x % 2 - - matches_matcher = AfterPreprocessing(parity, Equals(1)) - matches_matches = [3, 5] - matches_mismatches = [2] - - str_examples = [ - ("AfterPreprocessing(, Equals(1))", - AfterPreprocessing(parity, Equals(1))), - ] - - describe_examples = [ - ("1 != 0: after on 2", 2, - AfterPreprocessing(parity, Equals(1))), - ("1 != 0", 2, - AfterPreprocessing(parity, Equals(1), annotate=False)), - ] - -class TestMatchersAnyInterface(TestCase, TestMatchersInterface): - - matches_matcher = MatchesAny(DocTestMatches("1"), DocTestMatches("2")) - matches_matches = ["1", "2"] - matches_mismatches = ["3"] - - str_examples = [( - "MatchesAny(DocTestMatches('1\\n'), DocTestMatches('2\\n'))", - MatchesAny(DocTestMatches("1"), DocTestMatches("2"))), - ] - - describe_examples = [("""Differences: [ -Expected: - 1 -Got: - 3 - -Expected: - 2 -Got: - 3 - -]""", - "3", MatchesAny(DocTestMatches("1"), DocTestMatches("2")))] - - -class TestMatchesAllInterface(TestCase, TestMatchersInterface): - - matches_matcher = MatchesAll(NotEquals(1), NotEquals(2)) - matches_matches = [3, 4] - matches_mismatches = [1, 2] - - str_examples = [ - ("MatchesAll(NotEquals(1), NotEquals(2))", - MatchesAll(NotEquals(1), NotEquals(2)))] - - describe_examples = [ - ("""Differences: [ -1 == 1 -]""", - 1, MatchesAll(NotEquals(1), NotEquals(2))), - ("1 == 1", 1, - MatchesAll(NotEquals(2), NotEquals(1), Equals(3), first_only=True)), - ] - - -class TestAnnotate(TestCase, TestMatchersInterface): - - matches_matcher = Annotate("foo", Equals(1)) - matches_matches = [1] - matches_mismatches = [2] - - str_examples = [ - ("Annotate('foo', Equals(1))", Annotate("foo", Equals(1)))] - - describe_examples = [("1 != 2: foo", 2, Annotate('foo', Equals(1)))] - - def test_if_message_no_message(self): - # Annotate.if_message returns the given matcher if there is no - # message. - matcher = Equals(1) - not_annotated = Annotate.if_message('', matcher) - self.assertIs(matcher, not_annotated) - - def test_if_message_given_message(self): - # Annotate.if_message returns an annotated version of the matcher if a - # message is provided. - matcher = Equals(1) - expected = Annotate('foo', matcher) - annotated = Annotate.if_message('foo', matcher) - self.assertThat( - annotated, - MatchesStructure.fromExample(expected, 'annotation', 'matcher')) - - -class TestAnnotatedMismatch(TestCase): - - run_tests_with = FullStackRunTest - - def test_forwards_details(self): - x = Mismatch('description', {'foo': 'bar'}) - annotated = AnnotatedMismatch("annotation", x) - self.assertEqual(x.get_details(), annotated.get_details()) - - -class TestNotInterface(TestCase, TestMatchersInterface): - - matches_matcher = Not(Equals(1)) - matches_matches = [2] - matches_mismatches = [1] - - str_examples = [ - ("Not(Equals(1))", Not(Equals(1))), - ("Not(Equals('1'))", Not(Equals('1')))] - - describe_examples = [('1 matches Equals(1)', 1, Not(Equals(1)))] - - -def is_even(x): - return x % 2 == 0 - - -class TestMatchesPredicate(TestCase, TestMatchersInterface): - - matches_matcher = MatchesPredicate(is_even, "%s is not even") - matches_matches = [2, 4, 6, 8] - matches_mismatches = [3, 5, 7, 9] - - str_examples = [ - ("MatchesPredicate(%r, %r)" % (is_even, "%s is not even"), - MatchesPredicate(is_even, "%s is not even")), - ] - - describe_examples = [ - ('7 is not even', 7, MatchesPredicate(is_even, "%s is not even")), - ] - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/matchers/test_impl.py b/lib/testtools/testtools/tests/matchers/test_impl.py deleted file mode 100644 index 10967ead25b..00000000000 --- a/lib/testtools/testtools/tests/matchers/test_impl.py +++ /dev/null @@ -1,132 +0,0 @@ -# Copyright (c) 2008-2012 testtools developers. See LICENSE for details. - -"""Tests for matchers.""" - -from testtools import ( - Matcher, # check that Matcher is exposed at the top level for docs. - TestCase, - ) -from testtools.compat import ( - str_is_unicode, - text_repr, - _u, - ) -from testtools.matchers import ( - Equals, - MatchesException, - Raises, - ) -from testtools.matchers._impl import ( - Mismatch, - MismatchDecorator, - MismatchError, - ) -from testtools.tests.helpers import FullStackRunTest - -# Silence pyflakes. -Matcher - - -class TestMismatch(TestCase): - - run_tests_with = FullStackRunTest - - def test_constructor_arguments(self): - mismatch = Mismatch("some description", {'detail': "things"}) - self.assertEqual("some description", mismatch.describe()) - self.assertEqual({'detail': "things"}, mismatch.get_details()) - - def test_constructor_no_arguments(self): - mismatch = Mismatch() - self.assertThat(mismatch.describe, - Raises(MatchesException(NotImplementedError))) - self.assertEqual({}, mismatch.get_details()) - - -class TestMismatchError(TestCase): - - def test_is_assertion_error(self): - # MismatchError is an AssertionError, so that most of the time, it - # looks like a test failure, rather than an error. - def raise_mismatch_error(): - raise MismatchError(2, Equals(3), Equals(3).match(2)) - self.assertRaises(AssertionError, raise_mismatch_error) - - def test_default_description_is_mismatch(self): - mismatch = Equals(3).match(2) - e = MismatchError(2, Equals(3), mismatch) - self.assertEqual(mismatch.describe(), str(e)) - - def test_default_description_unicode(self): - matchee = _u('\xa7') - matcher = Equals(_u('a')) - mismatch = matcher.match(matchee) - e = MismatchError(matchee, matcher, mismatch) - self.assertEqual(mismatch.describe(), str(e)) - - def test_verbose_description(self): - matchee = 2 - matcher = Equals(3) - mismatch = matcher.match(2) - e = MismatchError(matchee, matcher, mismatch, True) - expected = ( - 'Match failed. Matchee: %r\n' - 'Matcher: %s\n' - 'Difference: %s\n' % ( - matchee, - matcher, - matcher.match(matchee).describe(), - )) - self.assertEqual(expected, str(e)) - - def test_verbose_unicode(self): - # When assertThat is given matchees or matchers that contain non-ASCII - # unicode strings, we can still provide a meaningful error. - matchee = _u('\xa7') - matcher = Equals(_u('a')) - mismatch = matcher.match(matchee) - expected = ( - 'Match failed. Matchee: %s\n' - 'Matcher: %s\n' - 'Difference: %s\n' % ( - text_repr(matchee), - matcher, - mismatch.describe(), - )) - e = MismatchError(matchee, matcher, mismatch, True) - if str_is_unicode: - actual = str(e) - else: - actual = unicode(e) - # Using str() should still work, and return ascii only - self.assertEqual( - expected.replace(matchee, matchee.encode("unicode-escape")), - str(e).decode("ascii")) - self.assertEqual(expected, actual) - - -class TestMismatchDecorator(TestCase): - - run_tests_with = FullStackRunTest - - def test_forwards_description(self): - x = Mismatch("description", {'foo': 'bar'}) - decorated = MismatchDecorator(x) - self.assertEqual(x.describe(), decorated.describe()) - - def test_forwards_details(self): - x = Mismatch("description", {'foo': 'bar'}) - decorated = MismatchDecorator(x) - self.assertEqual(x.get_details(), decorated.get_details()) - - def test_repr(self): - x = Mismatch("description", {'foo': 'bar'}) - decorated = MismatchDecorator(x) - self.assertEqual( - '' % (x,), - repr(decorated)) - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/test_compat.py b/lib/testtools/testtools/tests/test_compat.py deleted file mode 100644 index b29dc34f248..00000000000 --- a/lib/testtools/testtools/tests/test_compat.py +++ /dev/null @@ -1,432 +0,0 @@ -# Copyright (c) 2010 testtools developers. See LICENSE for details. - -"""Tests for miscellaneous compatibility functions""" - -import linecache -import os -import sys -import tempfile -import traceback - -import testtools - -from testtools.compat import ( - _b, - _detect_encoding, - _get_source_encoding, - _u, - reraise, - str_is_unicode, - text_repr, - unicode_output_stream, - ) -from testtools.matchers import ( - MatchesException, - Not, - Raises, - ) - - -class TestDetectEncoding(testtools.TestCase): - """Test detection of Python source encodings""" - - def _check_encoding(self, expected, lines, possibly_invalid=False): - """Check lines are valid Python and encoding is as expected""" - if not possibly_invalid: - compile(_b("".join(lines)), "", "exec") - encoding = _detect_encoding(lines) - self.assertEqual(expected, encoding, - "Encoding %r expected but got %r from lines %r" % - (expected, encoding, lines)) - - def test_examples_from_pep(self): - """Check the examples given in PEP 263 all work as specified - - See 'Examples' section of - """ - # With interpreter binary and using Emacs style file encoding comment: - self._check_encoding("latin-1", ( - "#!/usr/bin/python\n", - "# -*- coding: latin-1 -*-\n", - "import os, sys\n")) - self._check_encoding("iso-8859-15", ( - "#!/usr/bin/python\n", - "# -*- coding: iso-8859-15 -*-\n", - "import os, sys\n")) - self._check_encoding("ascii", ( - "#!/usr/bin/python\n", - "# -*- coding: ascii -*-\n", - "import os, sys\n")) - # Without interpreter line, using plain text: - self._check_encoding("utf-8", ( - "# This Python file uses the following encoding: utf-8\n", - "import os, sys\n")) - # Text editors might have different ways of defining the file's - # encoding, e.g. - self._check_encoding("latin-1", ( - "#!/usr/local/bin/python\n", - "# coding: latin-1\n", - "import os, sys\n")) - # Without encoding comment, Python's parser will assume ASCII text: - self._check_encoding("ascii", ( - "#!/usr/local/bin/python\n", - "import os, sys\n")) - # Encoding comments which don't work: - # Missing "coding:" prefix: - self._check_encoding("ascii", ( - "#!/usr/local/bin/python\n", - "# latin-1\n", - "import os, sys\n")) - # Encoding comment not on line 1 or 2: - self._check_encoding("ascii", ( - "#!/usr/local/bin/python\n", - "#\n", - "# -*- coding: latin-1 -*-\n", - "import os, sys\n")) - # Unsupported encoding: - self._check_encoding("ascii", ( - "#!/usr/local/bin/python\n", - "# -*- coding: utf-42 -*-\n", - "import os, sys\n"), - possibly_invalid=True) - - def test_bom(self): - """Test the UTF-8 BOM counts as an encoding declaration""" - self._check_encoding("utf-8", ( - "\xef\xbb\xbfimport sys\n", - )) - self._check_encoding("utf-8", ( - "\xef\xbb\xbf# File encoding: utf-8\n", - )) - self._check_encoding("utf-8", ( - '\xef\xbb\xbf"""Module docstring\n', - '\xef\xbb\xbfThat should just be a ZWNB"""\n')) - self._check_encoding("latin-1", ( - '"""Is this coding: latin-1 or coding: utf-8 instead?\n', - '\xef\xbb\xbfThose should be latin-1 bytes"""\n')) - self._check_encoding("utf-8", ( - "\xef\xbb\xbf# Is the coding: utf-8 or coding: euc-jp instead?\n", - '"""Module docstring say \xe2\x98\x86"""\n')) - - def test_multiple_coding_comments(self): - """Test only the first of multiple coding declarations counts""" - self._check_encoding("iso-8859-1", ( - "# Is the coding: iso-8859-1\n", - "# Or is it coding: iso-8859-2\n"), - possibly_invalid=True) - self._check_encoding("iso-8859-1", ( - "#!/usr/bin/python\n", - "# Is the coding: iso-8859-1\n", - "# Or is it coding: iso-8859-2\n")) - self._check_encoding("iso-8859-1", ( - "# Is the coding: iso-8859-1 or coding: iso-8859-2\n", - "# Or coding: iso-8859-3 or coding: iso-8859-4\n"), - possibly_invalid=True) - self._check_encoding("iso-8859-2", ( - "# Is the coding iso-8859-1 or coding: iso-8859-2\n", - "# Spot the missing colon above\n")) - - -class TestGetSourceEncoding(testtools.TestCase): - """Test reading and caching the encodings of source files""" - - def setUp(self): - testtools.TestCase.setUp(self) - dir = tempfile.mkdtemp() - self.addCleanup(os.rmdir, dir) - self.filename = os.path.join(dir, self.id().rsplit(".", 1)[1] + ".py") - self._written = False - - def put_source(self, text): - f = open(self.filename, "w") - try: - f.write(text) - finally: - f.close() - if not self._written: - self._written = True - self.addCleanup(os.remove, self.filename) - self.addCleanup(linecache.cache.pop, self.filename, None) - - def test_nonexistant_file_as_ascii(self): - """When file can't be found, the encoding should default to ascii""" - self.assertEquals("ascii", _get_source_encoding(self.filename)) - - def test_encoding_is_cached(self): - """The encoding should stay the same if the cache isn't invalidated""" - self.put_source( - "# coding: iso-8859-13\n" - "import os\n") - self.assertEquals("iso-8859-13", _get_source_encoding(self.filename)) - self.put_source( - "# coding: rot-13\n" - "vzcbeg bf\n") - self.assertEquals("iso-8859-13", _get_source_encoding(self.filename)) - - def test_traceback_rechecks_encoding(self): - """A traceback function checks the cache and resets the encoding""" - self.put_source( - "# coding: iso-8859-8\n" - "import os\n") - self.assertEquals("iso-8859-8", _get_source_encoding(self.filename)) - self.put_source( - "# coding: utf-8\n" - "import os\n") - try: - exec (compile("raise RuntimeError\n", self.filename, "exec")) - except RuntimeError: - traceback.extract_tb(sys.exc_info()[2]) - else: - self.fail("RuntimeError not raised") - self.assertEquals("utf-8", _get_source_encoding(self.filename)) - - -class _FakeOutputStream(object): - """A simple file-like object for testing""" - - def __init__(self): - self.writelog = [] - - def write(self, obj): - self.writelog.append(obj) - - -class TestUnicodeOutputStream(testtools.TestCase): - """Test wrapping output streams so they work with arbitrary unicode""" - - uni = _u("pa\u026a\u03b8\u0259n") - - def setUp(self): - super(TestUnicodeOutputStream, self).setUp() - if sys.platform == "cli": - self.skip("IronPython shouldn't wrap streams to do encoding") - - def test_no_encoding_becomes_ascii(self): - """A stream with no encoding attribute gets ascii/replace strings""" - sout = _FakeOutputStream() - unicode_output_stream(sout).write(self.uni) - self.assertEqual([_b("pa???n")], sout.writelog) - - def test_encoding_as_none_becomes_ascii(self): - """A stream with encoding value of None gets ascii/replace strings""" - sout = _FakeOutputStream() - sout.encoding = None - unicode_output_stream(sout).write(self.uni) - self.assertEqual([_b("pa???n")], sout.writelog) - - def test_bogus_encoding_becomes_ascii(self): - """A stream with a bogus encoding gets ascii/replace strings""" - sout = _FakeOutputStream() - sout.encoding = "bogus" - unicode_output_stream(sout).write(self.uni) - self.assertEqual([_b("pa???n")], sout.writelog) - - def test_partial_encoding_replace(self): - """A string which can be partly encoded correctly should be""" - sout = _FakeOutputStream() - sout.encoding = "iso-8859-7" - unicode_output_stream(sout).write(self.uni) - self.assertEqual([_b("pa?\xe8?n")], sout.writelog) - - @testtools.skipIf(str_is_unicode, "Tests behaviour when str is not unicode") - def test_unicode_encodings_wrapped_when_str_is_not_unicode(self): - """A unicode encoding is wrapped but needs no error handler""" - sout = _FakeOutputStream() - sout.encoding = "utf-8" - uout = unicode_output_stream(sout) - self.assertEqual(uout.errors, "strict") - uout.write(self.uni) - self.assertEqual([_b("pa\xc9\xaa\xce\xb8\xc9\x99n")], sout.writelog) - - @testtools.skipIf(not str_is_unicode, "Tests behaviour when str is unicode") - def test_unicode_encodings_not_wrapped_when_str_is_unicode(self): - # No wrapping needed if native str type is unicode - sout = _FakeOutputStream() - sout.encoding = "utf-8" - uout = unicode_output_stream(sout) - self.assertIs(uout, sout) - - def test_stringio(self): - """A StringIO object should maybe get an ascii native str type""" - try: - from cStringIO import StringIO - newio = False - except ImportError: - from io import StringIO - newio = True - sout = StringIO() - soutwrapper = unicode_output_stream(sout) - if newio: - self.expectFailure("Python 3 StringIO expects text not bytes", - self.assertThat, lambda: soutwrapper.write(self.uni), - Not(Raises(MatchesException(TypeError)))) - soutwrapper.write(self.uni) - self.assertEqual("pa???n", sout.getvalue()) - - -class TestTextRepr(testtools.TestCase): - """Ensure in extending repr, basic behaviours are not being broken""" - - ascii_examples = ( - # Single character examples - # C0 control codes should be escaped except multiline \n - ("\x00", "'\\x00'", "'''\\\n\\x00'''"), - ("\b", "'\\x08'", "'''\\\n\\x08'''"), - ("\t", "'\\t'", "'''\\\n\\t'''"), - ("\n", "'\\n'", "'''\\\n\n'''"), - ("\r", "'\\r'", "'''\\\n\\r'''"), - # Quotes and backslash should match normal repr behaviour - ('"', "'\"'", "'''\\\n\"'''"), - ("'", "\"'\"", "'''\\\n\\''''"), - ("\\", "'\\\\'", "'''\\\n\\\\'''"), - # DEL is also unprintable and should be escaped - ("\x7F", "'\\x7f'", "'''\\\n\\x7f'''"), - - # Character combinations that need double checking - ("\r\n", "'\\r\\n'", "'''\\\n\\r\n'''"), - ("\"'", "'\"\\''", "'''\\\n\"\\''''"), - ("'\"", "'\\'\"'", "'''\\\n'\"'''"), - ("\\n", "'\\\\n'", "'''\\\n\\\\n'''"), - ("\\\n", "'\\\\\\n'", "'''\\\n\\\\\n'''"), - ("\\' ", "\"\\\\' \"", "'''\\\n\\\\' '''"), - ("\\'\n", "\"\\\\'\\n\"", "'''\\\n\\\\'\n'''"), - ("\\'\"", "'\\\\\\'\"'", "'''\\\n\\\\'\"'''"), - ("\\'''", "\"\\\\'''\"", "'''\\\n\\\\\\'\\'\\''''"), - ) - - # Bytes with the high bit set should always be escaped - bytes_examples = ( - (_b("\x80"), "'\\x80'", "'''\\\n\\x80'''"), - (_b("\xA0"), "'\\xa0'", "'''\\\n\\xa0'''"), - (_b("\xC0"), "'\\xc0'", "'''\\\n\\xc0'''"), - (_b("\xFF"), "'\\xff'", "'''\\\n\\xff'''"), - (_b("\xC2\xA7"), "'\\xc2\\xa7'", "'''\\\n\\xc2\\xa7'''"), - ) - - # Unicode doesn't escape printable characters as per the Python 3 model - unicode_examples = ( - # C1 codes are unprintable - (_u("\x80"), "'\\x80'", "'''\\\n\\x80'''"), - (_u("\x9F"), "'\\x9f'", "'''\\\n\\x9f'''"), - # No-break space is unprintable - (_u("\xA0"), "'\\xa0'", "'''\\\n\\xa0'''"), - # Letters latin alphabets are printable - (_u("\xA1"), _u("'\xa1'"), _u("'''\\\n\xa1'''")), - (_u("\xFF"), _u("'\xff'"), _u("'''\\\n\xff'''")), - (_u("\u0100"), _u("'\u0100'"), _u("'''\\\n\u0100'''")), - # Line and paragraph seperators are unprintable - (_u("\u2028"), "'\\u2028'", "'''\\\n\\u2028'''"), - (_u("\u2029"), "'\\u2029'", "'''\\\n\\u2029'''"), - # Unpaired surrogates are unprintable - (_u("\uD800"), "'\\ud800'", "'''\\\n\\ud800'''"), - (_u("\uDFFF"), "'\\udfff'", "'''\\\n\\udfff'''"), - # Unprintable general categories not fully tested: Cc, Cf, Co, Cn, Zs - ) - - b_prefix = repr(_b(""))[:-2] - u_prefix = repr(_u(""))[:-2] - - def test_ascii_examples_oneline_bytes(self): - for s, expected, _ in self.ascii_examples: - b = _b(s) - actual = text_repr(b, multiline=False) - # Add self.assertIsInstance check? - self.assertEqual(actual, self.b_prefix + expected) - self.assertEqual(eval(actual), b) - - def test_ascii_examples_oneline_unicode(self): - for s, expected, _ in self.ascii_examples: - u = _u(s) - actual = text_repr(u, multiline=False) - self.assertEqual(actual, self.u_prefix + expected) - self.assertEqual(eval(actual), u) - - def test_ascii_examples_multiline_bytes(self): - for s, _, expected in self.ascii_examples: - b = _b(s) - actual = text_repr(b, multiline=True) - self.assertEqual(actual, self.b_prefix + expected) - self.assertEqual(eval(actual), b) - - def test_ascii_examples_multiline_unicode(self): - for s, _, expected in self.ascii_examples: - u = _u(s) - actual = text_repr(u, multiline=True) - self.assertEqual(actual, self.u_prefix + expected) - self.assertEqual(eval(actual), u) - - def test_ascii_examples_defaultline_bytes(self): - for s, one, multi in self.ascii_examples: - expected = "\n" in s and multi or one - self.assertEqual(text_repr(_b(s)), self.b_prefix + expected) - - def test_ascii_examples_defaultline_unicode(self): - for s, one, multi in self.ascii_examples: - expected = "\n" in s and multi or one - self.assertEqual(text_repr(_u(s)), self.u_prefix + expected) - - def test_bytes_examples_oneline(self): - for b, expected, _ in self.bytes_examples: - actual = text_repr(b, multiline=False) - self.assertEqual(actual, self.b_prefix + expected) - self.assertEqual(eval(actual), b) - - def test_bytes_examples_multiline(self): - for b, _, expected in self.bytes_examples: - actual = text_repr(b, multiline=True) - self.assertEqual(actual, self.b_prefix + expected) - self.assertEqual(eval(actual), b) - - def test_unicode_examples_oneline(self): - for u, expected, _ in self.unicode_examples: - actual = text_repr(u, multiline=False) - self.assertEqual(actual, self.u_prefix + expected) - self.assertEqual(eval(actual), u) - - def test_unicode_examples_multiline(self): - for u, _, expected in self.unicode_examples: - actual = text_repr(u, multiline=True) - self.assertEqual(actual, self.u_prefix + expected) - self.assertEqual(eval(actual), u) - - - -class TestReraise(testtools.TestCase): - """Tests for trivial reraise wrapper needed for Python 2/3 changes""" - - def test_exc_info(self): - """After reraise exc_info matches plus some extra traceback""" - try: - raise ValueError("Bad value") - except ValueError: - _exc_info = sys.exc_info() - try: - reraise(*_exc_info) - except ValueError: - _new_exc_info = sys.exc_info() - self.assertIs(_exc_info[0], _new_exc_info[0]) - self.assertIs(_exc_info[1], _new_exc_info[1]) - expected_tb = traceback.extract_tb(_exc_info[2]) - self.assertEqual(expected_tb, - traceback.extract_tb(_new_exc_info[2])[-len(expected_tb):]) - - def test_custom_exception_no_args(self): - """Reraising does not require args attribute to contain params""" - - class CustomException(Exception): - """Exception that expects and sets attrs but not args""" - - def __init__(self, value): - Exception.__init__(self) - self.value = value - - try: - raise CustomException("Some value") - except CustomException: - _exc_info = sys.exc_info() - self.assertRaises(CustomException, reraise, *_exc_info) - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/test_content.py b/lib/testtools/testtools/tests/test_content.py deleted file mode 100644 index bc72513aaab..00000000000 --- a/lib/testtools/testtools/tests/test_content.py +++ /dev/null @@ -1,277 +0,0 @@ -# Copyright (c) 2008-2012 testtools developers. See LICENSE for details. - -import json -import os -import tempfile -import unittest - -from testtools import TestCase -from testtools.compat import ( - _b, - _u, - BytesIO, - StringIO, - ) -from testtools.content import ( - attach_file, - Content, - content_from_file, - content_from_stream, - JSON, - json_content, - TracebackContent, - text_content, - ) -from testtools.content_type import ( - ContentType, - UTF8_TEXT, - ) -from testtools.matchers import ( - Equals, - MatchesException, - Raises, - raises, - ) -from testtools.tests.helpers import an_exc_info - - -raises_value_error = Raises(MatchesException(ValueError)) - - -class TestContent(TestCase): - - def test___init___None_errors(self): - self.assertThat(lambda: Content(None, None), raises_value_error) - self.assertThat( - lambda: Content(None, lambda: ["traceback"]), raises_value_error) - self.assertThat( - lambda: Content(ContentType("text", "traceback"), None), - raises_value_error) - - def test___init___sets_ivars(self): - content_type = ContentType("foo", "bar") - content = Content(content_type, lambda: ["bytes"]) - self.assertEqual(content_type, content.content_type) - self.assertEqual(["bytes"], list(content.iter_bytes())) - - def test___eq__(self): - content_type = ContentType("foo", "bar") - one_chunk = lambda: [_b("bytes")] - two_chunk = lambda: [_b("by"), _b("tes")] - content1 = Content(content_type, one_chunk) - content2 = Content(content_type, one_chunk) - content3 = Content(content_type, two_chunk) - content4 = Content(content_type, lambda: [_b("by"), _b("te")]) - content5 = Content(ContentType("f", "b"), two_chunk) - self.assertEqual(content1, content2) - self.assertEqual(content1, content3) - self.assertNotEqual(content1, content4) - self.assertNotEqual(content1, content5) - - def test___repr__(self): - content = Content(ContentType("application", "octet-stream"), - lambda: [_b("\x00bin"), _b("ary\xff")]) - self.assertIn("\\x00binary\\xff", repr(content)) - - def test_iter_text_not_text_errors(self): - content_type = ContentType("foo", "bar") - content = Content(content_type, lambda: ["bytes"]) - self.assertThat(content.iter_text, raises_value_error) - - def test_iter_text_decodes(self): - content_type = ContentType("text", "strange", {"charset": "utf8"}) - content = Content( - content_type, lambda: [_u("bytes\xea").encode("utf8")]) - self.assertEqual([_u("bytes\xea")], list(content.iter_text())) - - def test_iter_text_default_charset_iso_8859_1(self): - content_type = ContentType("text", "strange") - text = _u("bytes\xea") - iso_version = text.encode("ISO-8859-1") - content = Content(content_type, lambda: [iso_version]) - self.assertEqual([text], list(content.iter_text())) - - def test_as_text(self): - content_type = ContentType("text", "strange", {"charset": "utf8"}) - content = Content( - content_type, lambda: [_u("bytes\xea").encode("utf8")]) - self.assertEqual(_u("bytes\xea"), content.as_text()) - - def test_from_file(self): - fd, path = tempfile.mkstemp() - self.addCleanup(os.remove, path) - os.write(fd, _b('some data')) - os.close(fd) - content = content_from_file(path, UTF8_TEXT, chunk_size=2) - self.assertThat( - list(content.iter_bytes()), - Equals([_b('so'), _b('me'), _b(' d'), _b('at'), _b('a')])) - - def test_from_nonexistent_file(self): - directory = tempfile.mkdtemp() - nonexistent = os.path.join(directory, 'nonexistent-file') - content = content_from_file(nonexistent) - self.assertThat(content.iter_bytes, raises(IOError)) - - def test_from_file_default_type(self): - content = content_from_file('/nonexistent/path') - self.assertThat(content.content_type, Equals(UTF8_TEXT)) - - def test_from_file_eager_loading(self): - fd, path = tempfile.mkstemp() - os.write(fd, _b('some data')) - os.close(fd) - content = content_from_file(path, UTF8_TEXT, buffer_now=True) - os.remove(path) - self.assertThat( - ''.join(content.iter_text()), Equals('some data')) - - def test_from_file_with_simple_seek(self): - f = tempfile.NamedTemporaryFile() - f.write(_b('some data')) - f.flush() - self.addCleanup(f.close) - content = content_from_file( - f.name, UTF8_TEXT, chunk_size=50, seek_offset=5) - self.assertThat( - list(content.iter_bytes()), Equals([_b('data')])) - - def test_from_file_with_whence_seek(self): - f = tempfile.NamedTemporaryFile() - f.write(_b('some data')) - f.flush() - self.addCleanup(f.close) - content = content_from_file( - f.name, UTF8_TEXT, chunk_size=50, seek_offset=-4, seek_whence=2) - self.assertThat( - list(content.iter_bytes()), Equals([_b('data')])) - - def test_from_stream(self): - data = StringIO('some data') - content = content_from_stream(data, UTF8_TEXT, chunk_size=2) - self.assertThat( - list(content.iter_bytes()), Equals(['so', 'me', ' d', 'at', 'a'])) - - def test_from_stream_default_type(self): - data = StringIO('some data') - content = content_from_stream(data) - self.assertThat(content.content_type, Equals(UTF8_TEXT)) - - def test_from_stream_eager_loading(self): - fd, path = tempfile.mkstemp() - self.addCleanup(os.remove, path) - self.addCleanup(os.close, fd) - os.write(fd, _b('some data')) - stream = open(path, 'rb') - self.addCleanup(stream.close) - content = content_from_stream(stream, UTF8_TEXT, buffer_now=True) - os.write(fd, _b('more data')) - self.assertThat( - ''.join(content.iter_text()), Equals('some data')) - - def test_from_stream_with_simple_seek(self): - data = BytesIO(_b('some data')) - content = content_from_stream( - data, UTF8_TEXT, chunk_size=50, seek_offset=5) - self.assertThat( - list(content.iter_bytes()), Equals([_b('data')])) - - def test_from_stream_with_whence_seek(self): - data = BytesIO(_b('some data')) - content = content_from_stream( - data, UTF8_TEXT, chunk_size=50, seek_offset=-4, seek_whence=2) - self.assertThat( - list(content.iter_bytes()), Equals([_b('data')])) - - def test_from_text(self): - data = _u("some data") - expected = Content(UTF8_TEXT, lambda: [data.encode('utf8')]) - self.assertEqual(expected, text_content(data)) - - def test_json_content(self): - data = {'foo': 'bar'} - expected = Content(JSON, lambda: [_b('{"foo": "bar"}')]) - self.assertEqual(expected, json_content(data)) - - -class TestTracebackContent(TestCase): - - def test___init___None_errors(self): - self.assertThat( - lambda: TracebackContent(None, None), raises_value_error) - - def test___init___sets_ivars(self): - content = TracebackContent(an_exc_info, self) - content_type = ContentType("text", "x-traceback", - {"language": "python", "charset": "utf8"}) - self.assertEqual(content_type, content.content_type) - result = unittest.TestResult() - expected = result._exc_info_to_string(an_exc_info, self) - self.assertEqual(expected, ''.join(list(content.iter_text()))) - - -class TestAttachFile(TestCase): - - def make_file(self, data): - # GZ 2011-04-21: This helper could be useful for methods above trying - # to use mkstemp, but should handle write failures and - # always close the fd. There must be a better way. - fd, path = tempfile.mkstemp() - self.addCleanup(os.remove, path) - os.write(fd, _b(data)) - os.close(fd) - return path - - def test_simple(self): - class SomeTest(TestCase): - def test_foo(self): - pass - test = SomeTest('test_foo') - data = 'some data' - path = self.make_file(data) - my_content = text_content(data) - attach_file(test, path, name='foo') - self.assertEqual({'foo': my_content}, test.getDetails()) - - def test_optional_name(self): - # If no name is provided, attach_file just uses the base name of the - # file. - class SomeTest(TestCase): - def test_foo(self): - pass - test = SomeTest('test_foo') - path = self.make_file('some data') - base_path = os.path.basename(path) - attach_file(test, path) - self.assertEqual([base_path], list(test.getDetails())) - - def test_lazy_read(self): - class SomeTest(TestCase): - def test_foo(self): - pass - test = SomeTest('test_foo') - path = self.make_file('some data') - attach_file(test, path, name='foo', buffer_now=False) - content = test.getDetails()['foo'] - content_file = open(path, 'w') - content_file.write('new data') - content_file.close() - self.assertEqual(''.join(content.iter_text()), 'new data') - - def test_eager_read_by_default(self): - class SomeTest(TestCase): - def test_foo(self): - pass - test = SomeTest('test_foo') - path = self.make_file('some data') - attach_file(test, path, name='foo') - content = test.getDetails()['foo'] - content_file = open(path, 'w') - content_file.write('new data') - content_file.close() - self.assertEqual(''.join(content.iter_text()), 'some data') - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/test_content_type.py b/lib/testtools/testtools/tests/test_content_type.py deleted file mode 100644 index ecb8e3a72e2..00000000000 --- a/lib/testtools/testtools/tests/test_content_type.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (c) 2008, 2012 testtools developers. See LICENSE for details. - -from testtools import TestCase -from testtools.matchers import Equals, MatchesException, Raises -from testtools.content_type import ( - ContentType, - JSON, - UTF8_TEXT, - ) - - -class TestContentType(TestCase): - - def test___init___None_errors(self): - raises_value_error = Raises(MatchesException(ValueError)) - self.assertThat(lambda:ContentType(None, None), raises_value_error) - self.assertThat(lambda:ContentType(None, "traceback"), - raises_value_error) - self.assertThat(lambda:ContentType("text", None), raises_value_error) - - def test___init___sets_ivars(self): - content_type = ContentType("foo", "bar") - self.assertEqual("foo", content_type.type) - self.assertEqual("bar", content_type.subtype) - self.assertEqual({}, content_type.parameters) - - def test___init___with_parameters(self): - content_type = ContentType("foo", "bar", {"quux": "thing"}) - self.assertEqual({"quux": "thing"}, content_type.parameters) - - def test___eq__(self): - content_type1 = ContentType("foo", "bar", {"quux": "thing"}) - content_type2 = ContentType("foo", "bar", {"quux": "thing"}) - content_type3 = ContentType("foo", "bar", {"quux": "thing2"}) - self.assertTrue(content_type1.__eq__(content_type2)) - self.assertFalse(content_type1.__eq__(content_type3)) - - def test_basic_repr(self): - content_type = ContentType('text', 'plain') - self.assertThat(repr(content_type), Equals('text/plain')) - - def test_extended_repr(self): - content_type = ContentType( - 'text', 'plain', {'foo': 'bar', 'baz': 'qux'}) - self.assertThat( - repr(content_type), Equals('text/plain; baz="qux", foo="bar"')) - - -class TestBuiltinContentTypes(TestCase): - - def test_plain_text(self): - # The UTF8_TEXT content type represents UTF-8 encoded text/plain. - self.assertThat(UTF8_TEXT.type, Equals('text')) - self.assertThat(UTF8_TEXT.subtype, Equals('plain')) - self.assertThat(UTF8_TEXT.parameters, Equals({'charset': 'utf8'})) - - def test_json_content(self): - # The JSON content type represents implictly UTF-8 application/json. - self.assertThat(JSON.type, Equals('application')) - self.assertThat(JSON.subtype, Equals('json')) - self.assertThat(JSON.parameters, Equals({})) - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/test_deferredruntest.py b/lib/testtools/testtools/tests/test_deferredruntest.py deleted file mode 100644 index 3373c0636cb..00000000000 --- a/lib/testtools/testtools/tests/test_deferredruntest.py +++ /dev/null @@ -1,766 +0,0 @@ -# Copyright (c) 2010-2011 testtools developers. See LICENSE for details. - -"""Tests for the DeferredRunTest single test execution logic.""" - -import os -import signal - -from testtools import ( - skipIf, - TestCase, - TestResult, - ) -from testtools.content import ( - text_content, - ) -from testtools.helpers import try_import -from testtools.matchers import ( - Equals, - KeysEqual, - MatchesException, - Raises, - ) -from testtools.runtest import RunTest -from testtools.testresult.doubles import ExtendedTestResult -from testtools.tests.test_spinner import NeedsTwistedTestCase - -assert_fails_with = try_import('testtools.deferredruntest.assert_fails_with') -AsynchronousDeferredRunTest = try_import( - 'testtools.deferredruntest.AsynchronousDeferredRunTest') -flush_logged_errors = try_import( - 'testtools.deferredruntest.flush_logged_errors') -SynchronousDeferredRunTest = try_import( - 'testtools.deferredruntest.SynchronousDeferredRunTest') - -defer = try_import('twisted.internet.defer') -failure = try_import('twisted.python.failure') -log = try_import('twisted.python.log') -DelayedCall = try_import('twisted.internet.base.DelayedCall') - - -class X(object): - """Tests that we run as part of our tests, nested to avoid discovery.""" - - class Base(TestCase): - def setUp(self): - super(X.Base, self).setUp() - self.calls = ['setUp'] - self.addCleanup(self.calls.append, 'clean-up') - def test_something(self): - self.calls.append('test') - def tearDown(self): - self.calls.append('tearDown') - super(X.Base, self).tearDown() - - class ErrorInSetup(Base): - expected_calls = ['setUp', 'clean-up'] - expected_results = [('addError', RuntimeError)] - def setUp(self): - super(X.ErrorInSetup, self).setUp() - raise RuntimeError("Error in setUp") - - class ErrorInTest(Base): - expected_calls = ['setUp', 'tearDown', 'clean-up'] - expected_results = [('addError', RuntimeError)] - def test_something(self): - raise RuntimeError("Error in test") - - class FailureInTest(Base): - expected_calls = ['setUp', 'tearDown', 'clean-up'] - expected_results = [('addFailure', AssertionError)] - def test_something(self): - self.fail("test failed") - - class ErrorInTearDown(Base): - expected_calls = ['setUp', 'test', 'clean-up'] - expected_results = [('addError', RuntimeError)] - def tearDown(self): - raise RuntimeError("Error in tearDown") - - class ErrorInCleanup(Base): - expected_calls = ['setUp', 'test', 'tearDown', 'clean-up'] - expected_results = [('addError', ZeroDivisionError)] - def test_something(self): - self.calls.append('test') - self.addCleanup(lambda: 1/0) - - class TestIntegration(NeedsTwistedTestCase): - - def assertResultsMatch(self, test, result): - events = list(result._events) - self.assertEqual(('startTest', test), events.pop(0)) - for expected_result in test.expected_results: - result = events.pop(0) - if len(expected_result) == 1: - self.assertEqual((expected_result[0], test), result) - else: - self.assertEqual((expected_result[0], test), result[:2]) - error_type = expected_result[1] - self.assertIn(error_type.__name__, str(result[2])) - self.assertEqual([('stopTest', test)], events) - - def test_runner(self): - result = ExtendedTestResult() - test = self.test_factory('test_something', runTest=self.runner) - test.run(result) - self.assertEqual(test.calls, self.test_factory.expected_calls) - self.assertResultsMatch(test, result) - - -def make_integration_tests(): - from unittest import TestSuite - from testtools import clone_test_with_new_id - runners = [ - ('RunTest', RunTest), - ('SynchronousDeferredRunTest', SynchronousDeferredRunTest), - ('AsynchronousDeferredRunTest', AsynchronousDeferredRunTest), - ] - - tests = [ - X.ErrorInSetup, - X.ErrorInTest, - X.ErrorInTearDown, - X.FailureInTest, - X.ErrorInCleanup, - ] - base_test = X.TestIntegration('test_runner') - integration_tests = [] - for runner_name, runner in runners: - for test in tests: - new_test = clone_test_with_new_id( - base_test, '%s(%s, %s)' % ( - base_test.id(), - runner_name, - test.__name__)) - new_test.test_factory = test - new_test.runner = runner - integration_tests.append(new_test) - return TestSuite(integration_tests) - - -class TestSynchronousDeferredRunTest(NeedsTwistedTestCase): - - def make_result(self): - return ExtendedTestResult() - - def make_runner(self, test): - return SynchronousDeferredRunTest(test, test.exception_handlers) - - def test_success(self): - class SomeCase(TestCase): - def test_success(self): - return defer.succeed(None) - test = SomeCase('test_success') - runner = self.make_runner(test) - result = self.make_result() - runner.run(result) - self.assertThat( - result._events, Equals([ - ('startTest', test), - ('addSuccess', test), - ('stopTest', test)])) - - def test_failure(self): - class SomeCase(TestCase): - def test_failure(self): - return defer.maybeDeferred(self.fail, "Egads!") - test = SomeCase('test_failure') - runner = self.make_runner(test) - result = self.make_result() - runner.run(result) - self.assertThat( - [event[:2] for event in result._events], Equals([ - ('startTest', test), - ('addFailure', test), - ('stopTest', test)])) - - def test_setUp_followed_by_test(self): - class SomeCase(TestCase): - def setUp(self): - super(SomeCase, self).setUp() - return defer.succeed(None) - def test_failure(self): - return defer.maybeDeferred(self.fail, "Egads!") - test = SomeCase('test_failure') - runner = self.make_runner(test) - result = self.make_result() - runner.run(result) - self.assertThat( - [event[:2] for event in result._events], Equals([ - ('startTest', test), - ('addFailure', test), - ('stopTest', test)])) - - -class TestAsynchronousDeferredRunTest(NeedsTwistedTestCase): - - def make_reactor(self): - from twisted.internet import reactor - return reactor - - def make_result(self): - return ExtendedTestResult() - - def make_runner(self, test, timeout=None): - if timeout is None: - timeout = self.make_timeout() - return AsynchronousDeferredRunTest( - test, test.exception_handlers, timeout=timeout) - - def make_timeout(self): - return 0.005 - - def test_setUp_returns_deferred_that_fires_later(self): - # setUp can return a Deferred that might fire at any time. - # AsynchronousDeferredRunTest will not go on to running the test until - # the Deferred returned by setUp actually fires. - call_log = [] - marker = object() - d = defer.Deferred().addCallback(call_log.append) - class SomeCase(TestCase): - def setUp(self): - super(SomeCase, self).setUp() - call_log.append('setUp') - return d - def test_something(self): - call_log.append('test') - def fire_deferred(): - self.assertThat(call_log, Equals(['setUp'])) - d.callback(marker) - test = SomeCase('test_something') - timeout = self.make_timeout() - runner = self.make_runner(test, timeout=timeout) - result = self.make_result() - reactor = self.make_reactor() - reactor.callLater(timeout, fire_deferred) - runner.run(result) - self.assertThat(call_log, Equals(['setUp', marker, 'test'])) - - def test_calls_setUp_test_tearDown_in_sequence(self): - # setUp, the test method and tearDown can all return - # Deferreds. AsynchronousDeferredRunTest will make sure that each of - # these are run in turn, only going on to the next stage once the - # Deferred from the previous stage has fired. - call_log = [] - a = defer.Deferred() - a.addCallback(lambda x: call_log.append('a')) - b = defer.Deferred() - b.addCallback(lambda x: call_log.append('b')) - c = defer.Deferred() - c.addCallback(lambda x: call_log.append('c')) - class SomeCase(TestCase): - def setUp(self): - super(SomeCase, self).setUp() - call_log.append('setUp') - return a - def test_success(self): - call_log.append('test') - return b - def tearDown(self): - super(SomeCase, self).tearDown() - call_log.append('tearDown') - return c - test = SomeCase('test_success') - timeout = self.make_timeout() - runner = self.make_runner(test, timeout) - result = self.make_result() - reactor = self.make_reactor() - def fire_a(): - self.assertThat(call_log, Equals(['setUp'])) - a.callback(None) - def fire_b(): - self.assertThat(call_log, Equals(['setUp', 'a', 'test'])) - b.callback(None) - def fire_c(): - self.assertThat( - call_log, Equals(['setUp', 'a', 'test', 'b', 'tearDown'])) - c.callback(None) - reactor.callLater(timeout * 0.25, fire_a) - reactor.callLater(timeout * 0.5, fire_b) - reactor.callLater(timeout * 0.75, fire_c) - runner.run(result) - self.assertThat( - call_log, Equals(['setUp', 'a', 'test', 'b', 'tearDown', 'c'])) - - def test_async_cleanups(self): - # Cleanups added with addCleanup can return - # Deferreds. AsynchronousDeferredRunTest will run each of them in - # turn. - class SomeCase(TestCase): - def test_whatever(self): - pass - test = SomeCase('test_whatever') - call_log = [] - a = defer.Deferred().addCallback(lambda x: call_log.append('a')) - b = defer.Deferred().addCallback(lambda x: call_log.append('b')) - c = defer.Deferred().addCallback(lambda x: call_log.append('c')) - test.addCleanup(lambda: a) - test.addCleanup(lambda: b) - test.addCleanup(lambda: c) - def fire_a(): - self.assertThat(call_log, Equals([])) - a.callback(None) - def fire_b(): - self.assertThat(call_log, Equals(['a'])) - b.callback(None) - def fire_c(): - self.assertThat(call_log, Equals(['a', 'b'])) - c.callback(None) - timeout = self.make_timeout() - reactor = self.make_reactor() - reactor.callLater(timeout * 0.25, fire_a) - reactor.callLater(timeout * 0.5, fire_b) - reactor.callLater(timeout * 0.75, fire_c) - runner = self.make_runner(test, timeout) - result = self.make_result() - runner.run(result) - self.assertThat(call_log, Equals(['a', 'b', 'c'])) - - def test_clean_reactor(self): - # If there's cruft left over in the reactor, the test fails. - reactor = self.make_reactor() - timeout = self.make_timeout() - class SomeCase(TestCase): - def test_cruft(self): - reactor.callLater(timeout * 10.0, lambda: None) - test = SomeCase('test_cruft') - runner = self.make_runner(test, timeout) - result = self.make_result() - runner.run(result) - self.assertThat( - [event[:2] for event in result._events], - Equals( - [('startTest', test), - ('addError', test), - ('stopTest', test)])) - error = result._events[1][2] - self.assertThat(error, KeysEqual('traceback', 'twisted-log')) - - def test_exports_reactor(self): - # The reactor is set as an attribute on the test case. - reactor = self.make_reactor() - timeout = self.make_timeout() - class SomeCase(TestCase): - def test_cruft(self): - self.assertIs(reactor, self.reactor) - test = SomeCase('test_cruft') - runner = self.make_runner(test, timeout) - result = TestResult() - runner.run(result) - self.assertEqual([], result.errors) - self.assertEqual([], result.failures) - - def test_unhandled_error_from_deferred(self): - # If there's a Deferred with an unhandled error, the test fails. Each - # unhandled error is reported with a separate traceback. - class SomeCase(TestCase): - def test_cruft(self): - # Note we aren't returning the Deferred so that the error will - # be unhandled. - defer.maybeDeferred(lambda: 1/0) - defer.maybeDeferred(lambda: 2/0) - test = SomeCase('test_cruft') - runner = self.make_runner(test) - result = self.make_result() - runner.run(result) - error = result._events[1][2] - result._events[1] = ('addError', test, None) - self.assertThat(result._events, Equals( - [('startTest', test), - ('addError', test, None), - ('stopTest', test)])) - self.assertThat( - error, KeysEqual( - 'twisted-log', - 'unhandled-error-in-deferred', - 'unhandled-error-in-deferred-1', - )) - - def test_unhandled_error_from_deferred_combined_with_error(self): - # If there's a Deferred with an unhandled error, the test fails. Each - # unhandled error is reported with a separate traceback, and the error - # is still reported. - class SomeCase(TestCase): - def test_cruft(self): - # Note we aren't returning the Deferred so that the error will - # be unhandled. - defer.maybeDeferred(lambda: 1/0) - 2 / 0 - test = SomeCase('test_cruft') - runner = self.make_runner(test) - result = self.make_result() - runner.run(result) - error = result._events[1][2] - result._events[1] = ('addError', test, None) - self.assertThat(result._events, Equals( - [('startTest', test), - ('addError', test, None), - ('stopTest', test)])) - self.assertThat( - error, KeysEqual( - 'traceback', - 'twisted-log', - 'unhandled-error-in-deferred', - )) - - @skipIf(os.name != "posix", "Sending SIGINT with os.kill is posix only") - def test_keyboard_interrupt_stops_test_run(self): - # If we get a SIGINT during a test run, the test stops and no more - # tests run. - SIGINT = getattr(signal, 'SIGINT', None) - if not SIGINT: - raise self.skipTest("SIGINT unavailable") - class SomeCase(TestCase): - def test_pause(self): - return defer.Deferred() - test = SomeCase('test_pause') - reactor = self.make_reactor() - timeout = self.make_timeout() - runner = self.make_runner(test, timeout * 5) - result = self.make_result() - reactor.callLater(timeout, os.kill, os.getpid(), SIGINT) - self.assertThat(lambda:runner.run(result), - Raises(MatchesException(KeyboardInterrupt))) - - @skipIf(os.name != "posix", "Sending SIGINT with os.kill is posix only") - def test_fast_keyboard_interrupt_stops_test_run(self): - # If we get a SIGINT during a test run, the test stops and no more - # tests run. - SIGINT = getattr(signal, 'SIGINT', None) - if not SIGINT: - raise self.skipTest("SIGINT unavailable") - class SomeCase(TestCase): - def test_pause(self): - return defer.Deferred() - test = SomeCase('test_pause') - reactor = self.make_reactor() - timeout = self.make_timeout() - runner = self.make_runner(test, timeout * 5) - result = self.make_result() - reactor.callWhenRunning(os.kill, os.getpid(), SIGINT) - self.assertThat(lambda:runner.run(result), - Raises(MatchesException(KeyboardInterrupt))) - - def test_timeout_causes_test_error(self): - # If a test times out, it reports itself as having failed with a - # TimeoutError. - class SomeCase(TestCase): - def test_pause(self): - return defer.Deferred() - test = SomeCase('test_pause') - runner = self.make_runner(test) - result = self.make_result() - runner.run(result) - error = result._events[1][2] - self.assertThat( - [event[:2] for event in result._events], Equals( - [('startTest', test), - ('addError', test), - ('stopTest', test)])) - self.assertIn('TimeoutError', str(error['traceback'])) - - def test_convenient_construction(self): - # As a convenience method, AsynchronousDeferredRunTest has a - # classmethod that returns an AsynchronousDeferredRunTest - # factory. This factory has the same API as the RunTest constructor. - reactor = object() - timeout = object() - handler = object() - factory = AsynchronousDeferredRunTest.make_factory(reactor, timeout) - runner = factory(self, [handler]) - self.assertIs(reactor, runner._reactor) - self.assertIs(timeout, runner._timeout) - self.assertIs(self, runner.case) - self.assertEqual([handler], runner.handlers) - - def test_use_convenient_factory(self): - # Make sure that the factory can actually be used. - factory = AsynchronousDeferredRunTest.make_factory() - class SomeCase(TestCase): - run_tests_with = factory - def test_something(self): - pass - case = SomeCase('test_something') - case.run() - - def test_convenient_construction_default_reactor(self): - # As a convenience method, AsynchronousDeferredRunTest has a - # classmethod that returns an AsynchronousDeferredRunTest - # factory. This factory has the same API as the RunTest constructor. - reactor = object() - handler = object() - factory = AsynchronousDeferredRunTest.make_factory(reactor=reactor) - runner = factory(self, [handler]) - self.assertIs(reactor, runner._reactor) - self.assertIs(self, runner.case) - self.assertEqual([handler], runner.handlers) - - def test_convenient_construction_default_timeout(self): - # As a convenience method, AsynchronousDeferredRunTest has a - # classmethod that returns an AsynchronousDeferredRunTest - # factory. This factory has the same API as the RunTest constructor. - timeout = object() - handler = object() - factory = AsynchronousDeferredRunTest.make_factory(timeout=timeout) - runner = factory(self, [handler]) - self.assertIs(timeout, runner._timeout) - self.assertIs(self, runner.case) - self.assertEqual([handler], runner.handlers) - - def test_convenient_construction_default_debugging(self): - # As a convenience method, AsynchronousDeferredRunTest has a - # classmethod that returns an AsynchronousDeferredRunTest - # factory. This factory has the same API as the RunTest constructor. - handler = object() - factory = AsynchronousDeferredRunTest.make_factory(debug=True) - runner = factory(self, [handler]) - self.assertIs(self, runner.case) - self.assertEqual([handler], runner.handlers) - self.assertEqual(True, runner._debug) - - def test_deferred_error(self): - class SomeTest(TestCase): - def test_something(self): - return defer.maybeDeferred(lambda: 1/0) - test = SomeTest('test_something') - runner = self.make_runner(test) - result = self.make_result() - runner.run(result) - self.assertThat( - [event[:2] for event in result._events], - Equals([ - ('startTest', test), - ('addError', test), - ('stopTest', test)])) - error = result._events[1][2] - self.assertThat(error, KeysEqual('traceback', 'twisted-log')) - - def test_only_addError_once(self): - # Even if the reactor is unclean and the test raises an error and the - # cleanups raise errors, we only called addError once per test. - reactor = self.make_reactor() - class WhenItRains(TestCase): - def it_pours(self): - # Add a dirty cleanup. - self.addCleanup(lambda: 3 / 0) - # Dirty the reactor. - from twisted.internet.protocol import ServerFactory - reactor.listenTCP(0, ServerFactory()) - # Unhandled error. - defer.maybeDeferred(lambda: 2 / 0) - # Actual error. - raise RuntimeError("Excess precipitation") - test = WhenItRains('it_pours') - runner = self.make_runner(test) - result = self.make_result() - runner.run(result) - self.assertThat( - [event[:2] for event in result._events], - Equals([ - ('startTest', test), - ('addError', test), - ('stopTest', test)])) - error = result._events[1][2] - self.assertThat( - error, KeysEqual( - 'traceback', - 'traceback-1', - 'traceback-2', - 'twisted-log', - 'unhandled-error-in-deferred', - )) - - def test_log_err_is_error(self): - # An error logged during the test run is recorded as an error in the - # tests. - class LogAnError(TestCase): - def test_something(self): - try: - 1/0 - except ZeroDivisionError: - f = failure.Failure() - log.err(f) - test = LogAnError('test_something') - runner = self.make_runner(test) - result = self.make_result() - runner.run(result) - self.assertThat( - [event[:2] for event in result._events], - Equals([ - ('startTest', test), - ('addError', test), - ('stopTest', test)])) - error = result._events[1][2] - self.assertThat(error, KeysEqual('logged-error', 'twisted-log')) - - def test_log_err_flushed_is_success(self): - # An error logged during the test run is recorded as an error in the - # tests. - class LogAnError(TestCase): - def test_something(self): - try: - 1/0 - except ZeroDivisionError: - f = failure.Failure() - log.err(f) - flush_logged_errors(ZeroDivisionError) - test = LogAnError('test_something') - runner = self.make_runner(test) - result = self.make_result() - runner.run(result) - self.assertThat( - result._events, - Equals([ - ('startTest', test), - ('addSuccess', test, {'twisted-log': text_content('')}), - ('stopTest', test)])) - - def test_log_in_details(self): - class LogAnError(TestCase): - def test_something(self): - log.msg("foo") - 1/0 - test = LogAnError('test_something') - runner = self.make_runner(test) - result = self.make_result() - runner.run(result) - self.assertThat( - [event[:2] for event in result._events], - Equals([ - ('startTest', test), - ('addError', test), - ('stopTest', test)])) - error = result._events[1][2] - self.assertThat(error, KeysEqual('traceback', 'twisted-log')) - - def test_debugging_unchanged_during_test_by_default(self): - debugging = [(defer.Deferred.debug, DelayedCall.debug)] - class SomeCase(TestCase): - def test_debugging_enabled(self): - debugging.append((defer.Deferred.debug, DelayedCall.debug)) - test = SomeCase('test_debugging_enabled') - runner = AsynchronousDeferredRunTest( - test, handlers=test.exception_handlers, - reactor=self.make_reactor(), timeout=self.make_timeout()) - runner.run(self.make_result()) - self.assertEqual(debugging[0], debugging[1]) - - def test_debugging_enabled_during_test_with_debug_flag(self): - self.patch(defer.Deferred, 'debug', False) - self.patch(DelayedCall, 'debug', False) - debugging = [] - class SomeCase(TestCase): - def test_debugging_enabled(self): - debugging.append((defer.Deferred.debug, DelayedCall.debug)) - test = SomeCase('test_debugging_enabled') - runner = AsynchronousDeferredRunTest( - test, handlers=test.exception_handlers, - reactor=self.make_reactor(), timeout=self.make_timeout(), - debug=True) - runner.run(self.make_result()) - self.assertEqual([(True, True)], debugging) - self.assertEqual(False, defer.Deferred.debug) - self.assertEqual(False, defer.Deferred.debug) - - -class TestAssertFailsWith(NeedsTwistedTestCase): - """Tests for `assert_fails_with`.""" - - if SynchronousDeferredRunTest is not None: - run_tests_with = SynchronousDeferredRunTest - - def test_assert_fails_with_success(self): - # assert_fails_with fails the test if it's given a Deferred that - # succeeds. - marker = object() - d = assert_fails_with(defer.succeed(marker), RuntimeError) - def check_result(failure): - failure.trap(self.failureException) - self.assertThat( - str(failure.value), - Equals("RuntimeError not raised (%r returned)" % (marker,))) - d.addCallbacks( - lambda x: self.fail("Should not have succeeded"), check_result) - return d - - def test_assert_fails_with_success_multiple_types(self): - # assert_fails_with fails the test if it's given a Deferred that - # succeeds. - marker = object() - d = assert_fails_with( - defer.succeed(marker), RuntimeError, ZeroDivisionError) - def check_result(failure): - failure.trap(self.failureException) - self.assertThat( - str(failure.value), - Equals("RuntimeError, ZeroDivisionError not raised " - "(%r returned)" % (marker,))) - d.addCallbacks( - lambda x: self.fail("Should not have succeeded"), check_result) - return d - - def test_assert_fails_with_wrong_exception(self): - # assert_fails_with fails the test if it's given a Deferred that - # succeeds. - d = assert_fails_with( - defer.maybeDeferred(lambda: 1/0), RuntimeError, KeyboardInterrupt) - def check_result(failure): - failure.trap(self.failureException) - lines = str(failure.value).splitlines() - self.assertThat( - lines[:2], - Equals([ - ("ZeroDivisionError raised instead of RuntimeError, " - "KeyboardInterrupt:"), - " Traceback (most recent call last):", - ])) - d.addCallbacks( - lambda x: self.fail("Should not have succeeded"), check_result) - return d - - def test_assert_fails_with_expected_exception(self): - # assert_fails_with calls back with the value of the failure if it's - # one of the expected types of failures. - try: - 1/0 - except ZeroDivisionError: - f = failure.Failure() - d = assert_fails_with(defer.fail(f), ZeroDivisionError) - return d.addCallback(self.assertThat, Equals(f.value)) - - def test_custom_failure_exception(self): - # If assert_fails_with is passed a 'failureException' keyword - # argument, then it will raise that instead of `AssertionError`. - class CustomException(Exception): - pass - marker = object() - d = assert_fails_with( - defer.succeed(marker), RuntimeError, - failureException=CustomException) - def check_result(failure): - failure.trap(CustomException) - self.assertThat( - str(failure.value), - Equals("RuntimeError not raised (%r returned)" % (marker,))) - return d.addCallbacks( - lambda x: self.fail("Should not have succeeded"), check_result) - - -class TestRunWithLogObservers(NeedsTwistedTestCase): - - def test_restores_observers(self): - from testtools.deferredruntest import run_with_log_observers - from twisted.python import log - # Make sure there's at least one observer. This reproduces bug - # #926189. - log.addObserver(lambda *args: None) - observers = list(log.theLogPublisher.observers) - run_with_log_observers([], lambda: None) - self.assertEqual(observers, log.theLogPublisher.observers) - - -def test_suite(): - from unittest import TestLoader, TestSuite - return TestSuite( - [TestLoader().loadTestsFromName(__name__), - make_integration_tests()]) diff --git a/lib/testtools/testtools/tests/test_distutilscmd.py b/lib/testtools/testtools/tests/test_distutilscmd.py deleted file mode 100644 index 59762dfd688..00000000000 --- a/lib/testtools/testtools/tests/test_distutilscmd.py +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright (c) 2010-2011 Testtools authors. See LICENSE for details. - -"""Tests for the distutils test command logic.""" - -from distutils.dist import Distribution - -from testtools.compat import ( - _b, - _u, - BytesIO, - ) -from testtools.helpers import try_import -fixtures = try_import('fixtures') - -import testtools -from testtools import TestCase -from testtools.distutilscmd import TestCommand -from testtools.matchers import MatchesRegex - - -if fixtures: - class SampleTestFixture(fixtures.Fixture): - """Creates testtools.runexample temporarily.""" - - def __init__(self): - self.package = fixtures.PythonPackage( - 'runexample', [('__init__.py', _b(""" -from testtools import TestCase - -class TestFoo(TestCase): - def test_bar(self): - pass - def test_quux(self): - pass -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) -"""))]) - - def setUp(self): - super(SampleTestFixture, self).setUp() - self.useFixture(self.package) - testtools.__path__.append(self.package.base) - self.addCleanup(testtools.__path__.remove, self.package.base) - - -class TestCommandTest(TestCase): - - def setUp(self): - super(TestCommandTest, self).setUp() - if fixtures is None: - self.skipTest("Need fixtures") - - def test_test_module(self): - self.useFixture(SampleTestFixture()) - stdout = self.useFixture(fixtures.StringStream('stdout')) - dist = Distribution() - dist.script_name = 'setup.py' - dist.script_args = ['test'] - dist.cmdclass = {'test': TestCommand} - dist.command_options = { - 'test': {'test_module': ('command line', 'testtools.runexample')}} - cmd = dist.reinitialize_command('test') - with fixtures.MonkeyPatch('sys.stdout', stdout.stream): - dist.run_command('test') - self.assertThat( - stdout.getDetails()['stdout'].as_text(), - MatchesRegex(_u("""Tests running... - -Ran 2 tests in \\d.\\d\\d\\ds -OK -"""))) - - def test_test_suite(self): - self.useFixture(SampleTestFixture()) - stdout = self.useFixture(fixtures.StringStream('stdout')) - dist = Distribution() - dist.script_name = 'setup.py' - dist.script_args = ['test'] - dist.cmdclass = {'test': TestCommand} - dist.command_options = { - 'test': { - 'test_suite': ( - 'command line', 'testtools.runexample.test_suite')}} - cmd = dist.reinitialize_command('test') - with fixtures.MonkeyPatch('sys.stdout', stdout.stream): - dist.run_command('test') - self.assertThat( - stdout.getDetails()['stdout'].as_text(), - MatchesRegex(_u("""Tests running... - -Ran 2 tests in \\d.\\d\\d\\ds -OK -"""))) - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/test_fixturesupport.py b/lib/testtools/testtools/tests/test_fixturesupport.py deleted file mode 100644 index cff9eb4c2f2..00000000000 --- a/lib/testtools/testtools/tests/test_fixturesupport.py +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright (c) 2010-2011 testtools developers. See LICENSE for details. - -import unittest - -from testtools import ( - TestCase, - content, - content_type, - ) -from testtools.compat import _b, _u -from testtools.helpers import try_import -from testtools.testresult.doubles import ( - ExtendedTestResult, - ) - -fixtures = try_import('fixtures') -LoggingFixture = try_import('fixtures.tests.helpers.LoggingFixture') - - -class TestFixtureSupport(TestCase): - - def setUp(self): - super(TestFixtureSupport, self).setUp() - if fixtures is None or LoggingFixture is None: - self.skipTest("Need fixtures") - - def test_useFixture(self): - fixture = LoggingFixture() - class SimpleTest(TestCase): - def test_foo(self): - self.useFixture(fixture) - result = unittest.TestResult() - SimpleTest('test_foo').run(result) - self.assertTrue(result.wasSuccessful()) - self.assertEqual(['setUp', 'cleanUp'], fixture.calls) - - def test_useFixture_cleanups_raise_caught(self): - calls = [] - def raiser(ignored): - calls.append('called') - raise Exception('foo') - fixture = fixtures.FunctionFixture(lambda:None, raiser) - class SimpleTest(TestCase): - def test_foo(self): - self.useFixture(fixture) - result = unittest.TestResult() - SimpleTest('test_foo').run(result) - self.assertFalse(result.wasSuccessful()) - self.assertEqual(['called'], calls) - - def test_useFixture_details_captured(self): - class DetailsFixture(fixtures.Fixture): - def setUp(self): - fixtures.Fixture.setUp(self) - self.addCleanup(delattr, self, 'content') - self.content = [_b('content available until cleanUp')] - self.addDetail('content', - content.Content(content_type.UTF8_TEXT, self.get_content)) - def get_content(self): - return self.content - fixture = DetailsFixture() - class SimpleTest(TestCase): - def test_foo(self): - self.useFixture(fixture) - # Add a colliding detail (both should show up) - self.addDetail('content', - content.Content(content_type.UTF8_TEXT, lambda:[_b('foo')])) - result = ExtendedTestResult() - SimpleTest('test_foo').run(result) - self.assertEqual('addSuccess', result._events[-2][0]) - details = result._events[-2][2] - self.assertEqual(['content', 'content-1'], sorted(details.keys())) - self.assertEqual('foo', details['content'].as_text()) - self.assertEqual('content available until cleanUp', - details['content-1'].as_text()) - - def test_useFixture_multiple_details_captured(self): - class DetailsFixture(fixtures.Fixture): - def setUp(self): - fixtures.Fixture.setUp(self) - self.addDetail('aaa', content.text_content("foo")) - self.addDetail('bbb', content.text_content("bar")) - fixture = DetailsFixture() - class SimpleTest(TestCase): - def test_foo(self): - self.useFixture(fixture) - result = ExtendedTestResult() - SimpleTest('test_foo').run(result) - self.assertEqual('addSuccess', result._events[-2][0]) - details = result._events[-2][2] - self.assertEqual(['aaa', 'bbb'], sorted(details)) - self.assertEqual(_u('foo'), details['aaa'].as_text()) - self.assertEqual(_u('bar'), details['bbb'].as_text()) - - def test_useFixture_details_captured_from_setUp(self): - # Details added during fixture set-up are gathered even if setUp() - # fails with an exception. - class BrokenFixture(fixtures.Fixture): - def setUp(self): - fixtures.Fixture.setUp(self) - self.addDetail('content', content.text_content("foobar")) - raise Exception() - fixture = BrokenFixture() - class SimpleTest(TestCase): - def test_foo(self): - self.useFixture(fixture) - result = ExtendedTestResult() - SimpleTest('test_foo').run(result) - self.assertEqual('addError', result._events[-2][0]) - details = result._events[-2][2] - self.assertEqual(['content', 'traceback'], sorted(details)) - self.assertEqual('foobar', ''.join(details['content'].iter_text())) - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/test_helpers.py b/lib/testtools/testtools/tests/test_helpers.py deleted file mode 100644 index 98da534bd20..00000000000 --- a/lib/testtools/testtools/tests/test_helpers.py +++ /dev/null @@ -1,213 +0,0 @@ -# Copyright (c) 2010-2012 testtools developers. See LICENSE for details. - -from testtools import TestCase -from testtools.helpers import ( - try_import, - try_imports, - ) -from testtools.matchers import ( - Equals, - Is, - Not, - ) -from testtools.tests.helpers import ( - FullStackRunTest, - hide_testtools_stack, - is_stack_hidden, - safe_hasattr, - ) - - -def check_error_callback(test, function, arg, expected_error_count, - expect_result): - """General test template for error_callback argument. - - :param test: Test case instance. - :param function: Either try_import or try_imports. - :param arg: Name or names to import. - :param expected_error_count: Expected number of calls to the callback. - :param expect_result: Boolean for whether a module should - ultimately be returned or not. - """ - cb_calls = [] - def cb(e): - test.assertIsInstance(e, ImportError) - cb_calls.append(e) - try: - result = function(arg, error_callback=cb) - except ImportError: - test.assertFalse(expect_result) - else: - if expect_result: - test.assertThat(result, Not(Is(None))) - else: - test.assertThat(result, Is(None)) - test.assertEquals(len(cb_calls), expected_error_count) - - -class TestSafeHasattr(TestCase): - - def test_attribute_not_there(self): - class Foo(object): - pass - self.assertEqual(False, safe_hasattr(Foo(), 'anything')) - - def test_attribute_there(self): - class Foo(object): - pass - foo = Foo() - foo.attribute = None - self.assertEqual(True, safe_hasattr(foo, 'attribute')) - - def test_property_there(self): - class Foo(object): - @property - def attribute(self): - return None - foo = Foo() - self.assertEqual(True, safe_hasattr(foo, 'attribute')) - - def test_property_raises(self): - class Foo(object): - @property - def attribute(self): - 1/0 - foo = Foo() - self.assertRaises(ZeroDivisionError, safe_hasattr, foo, 'attribute') - - -class TestTryImport(TestCase): - - def test_doesnt_exist(self): - # try_import('thing', foo) returns foo if 'thing' doesn't exist. - marker = object() - result = try_import('doesntexist', marker) - self.assertThat(result, Is(marker)) - - def test_None_is_default_alternative(self): - # try_import('thing') returns None if 'thing' doesn't exist. - result = try_import('doesntexist') - self.assertThat(result, Is(None)) - - def test_existing_module(self): - # try_import('thing', foo) imports 'thing' and returns it if it's a - # module that exists. - result = try_import('os', object()) - import os - self.assertThat(result, Is(os)) - - def test_existing_submodule(self): - # try_import('thing.another', foo) imports 'thing' and returns it if - # it's a module that exists. - result = try_import('os.path', object()) - import os - self.assertThat(result, Is(os.path)) - - def test_nonexistent_submodule(self): - # try_import('thing.another', foo) imports 'thing' and returns foo if - # 'another' doesn't exist. - marker = object() - result = try_import('os.doesntexist', marker) - self.assertThat(result, Is(marker)) - - def test_object_from_module(self): - # try_import('thing.object') imports 'thing' and returns - # 'thing.object' if 'thing' is a module and 'object' is not. - result = try_import('os.path.join') - import os - self.assertThat(result, Is(os.path.join)) - - def test_error_callback(self): - # the error callback is called on failures. - check_error_callback(self, try_import, 'doesntexist', 1, False) - - def test_error_callback_missing_module_member(self): - # the error callback is called on failures to find an object - # inside an existing module. - check_error_callback(self, try_import, 'os.nonexistent', 1, False) - - def test_error_callback_not_on_success(self): - # the error callback is not called on success. - check_error_callback(self, try_import, 'os.path', 0, True) - - -class TestTryImports(TestCase): - - def test_doesnt_exist(self): - # try_imports('thing', foo) returns foo if 'thing' doesn't exist. - marker = object() - result = try_imports(['doesntexist'], marker) - self.assertThat(result, Is(marker)) - - def test_fallback(self): - result = try_imports(['doesntexist', 'os']) - import os - self.assertThat(result, Is(os)) - - def test_None_is_default_alternative(self): - # try_imports('thing') returns None if 'thing' doesn't exist. - e = self.assertRaises( - ImportError, try_imports, ['doesntexist', 'noreally']) - self.assertThat( - str(e), - Equals("Could not import any of: doesntexist, noreally")) - - def test_existing_module(self): - # try_imports('thing', foo) imports 'thing' and returns it if it's a - # module that exists. - result = try_imports(['os'], object()) - import os - self.assertThat(result, Is(os)) - - def test_existing_submodule(self): - # try_imports('thing.another', foo) imports 'thing' and returns it if - # it's a module that exists. - result = try_imports(['os.path'], object()) - import os - self.assertThat(result, Is(os.path)) - - def test_nonexistent_submodule(self): - # try_imports('thing.another', foo) imports 'thing' and returns foo if - # 'another' doesn't exist. - marker = object() - result = try_imports(['os.doesntexist'], marker) - self.assertThat(result, Is(marker)) - - def test_fallback_submodule(self): - result = try_imports(['os.doesntexist', 'os.path']) - import os - self.assertThat(result, Is(os.path)) - - def test_error_callback(self): - # One error for every class that doesn't exist. - check_error_callback(self, try_imports, - ['os.doesntexist', 'os.notthiseither'], - 2, False) - check_error_callback(self, try_imports, - ['os.doesntexist', 'os.notthiseither', 'os'], - 2, True) - check_error_callback(self, try_imports, - ['os.path'], - 0, True) - - -class TestStackHiding(TestCase): - - run_tests_with = FullStackRunTest - - def setUp(self): - super(TestStackHiding, self).setUp() - self.addCleanup(hide_testtools_stack, is_stack_hidden()) - - def test_is_stack_hidden_consistent_true(self): - hide_testtools_stack(True) - self.assertEqual(True, is_stack_hidden()) - - def test_is_stack_hidden_consistent_false(self): - hide_testtools_stack(False) - self.assertEqual(False, is_stack_hidden()) - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/test_monkey.py b/lib/testtools/testtools/tests/test_monkey.py deleted file mode 100644 index 540a2ee909f..00000000000 --- a/lib/testtools/testtools/tests/test_monkey.py +++ /dev/null @@ -1,167 +0,0 @@ -# Copyright (c) 2010 Twisted Matrix Laboratories. -# See LICENSE for details. - -"""Tests for testtools.monkey.""" - -from testtools import TestCase -from testtools.matchers import MatchesException, Raises -from testtools.monkey import MonkeyPatcher, patch - - -class TestObj: - - def __init__(self): - self.foo = 'foo value' - self.bar = 'bar value' - self.baz = 'baz value' - - -class MonkeyPatcherTest(TestCase): - """ - Tests for 'MonkeyPatcher' monkey-patching class. - """ - - def setUp(self): - super(MonkeyPatcherTest, self).setUp() - self.test_object = TestObj() - self.original_object = TestObj() - self.monkey_patcher = MonkeyPatcher() - - def test_empty(self): - # A monkey patcher without patches doesn't change a thing. - self.monkey_patcher.patch() - - # We can't assert that all state is unchanged, but at least we can - # check our test object. - self.assertEquals(self.original_object.foo, self.test_object.foo) - self.assertEquals(self.original_object.bar, self.test_object.bar) - self.assertEquals(self.original_object.baz, self.test_object.baz) - - def test_construct_with_patches(self): - # Constructing a 'MonkeyPatcher' with patches adds all of the given - # patches to the patch list. - patcher = MonkeyPatcher((self.test_object, 'foo', 'haha'), - (self.test_object, 'bar', 'hehe')) - patcher.patch() - self.assertEquals('haha', self.test_object.foo) - self.assertEquals('hehe', self.test_object.bar) - self.assertEquals(self.original_object.baz, self.test_object.baz) - - def test_patch_existing(self): - # Patching an attribute that exists sets it to the value defined in the - # patch. - self.monkey_patcher.add_patch(self.test_object, 'foo', 'haha') - self.monkey_patcher.patch() - self.assertEquals(self.test_object.foo, 'haha') - - def test_patch_non_existing(self): - # Patching a non-existing attribute sets it to the value defined in - # the patch. - self.monkey_patcher.add_patch(self.test_object, 'doesntexist', 'value') - self.monkey_patcher.patch() - self.assertEquals(self.test_object.doesntexist, 'value') - - def test_restore_non_existing(self): - # Restoring a value that didn't exist before the patch deletes the - # value. - self.monkey_patcher.add_patch(self.test_object, 'doesntexist', 'value') - self.monkey_patcher.patch() - self.monkey_patcher.restore() - marker = object() - self.assertIs(marker, getattr(self.test_object, 'doesntexist', marker)) - - def test_patch_already_patched(self): - # Adding a patch for an object and attribute that already have a patch - # overrides the existing patch. - self.monkey_patcher.add_patch(self.test_object, 'foo', 'blah') - self.monkey_patcher.add_patch(self.test_object, 'foo', 'BLAH') - self.monkey_patcher.patch() - self.assertEquals(self.test_object.foo, 'BLAH') - self.monkey_patcher.restore() - self.assertEquals(self.test_object.foo, self.original_object.foo) - - def test_restore_twice_is_a_no_op(self): - # Restoring an already-restored monkey patch is a no-op. - self.monkey_patcher.add_patch(self.test_object, 'foo', 'blah') - self.monkey_patcher.patch() - self.monkey_patcher.restore() - self.assertEquals(self.test_object.foo, self.original_object.foo) - self.monkey_patcher.restore() - self.assertEquals(self.test_object.foo, self.original_object.foo) - - def test_run_with_patches_decoration(self): - # run_with_patches runs the given callable, passing in all arguments - # and keyword arguments, and returns the return value of the callable. - log = [] - - def f(a, b, c=None): - log.append((a, b, c)) - return 'foo' - - result = self.monkey_patcher.run_with_patches(f, 1, 2, c=10) - self.assertEquals('foo', result) - self.assertEquals([(1, 2, 10)], log) - - def test_repeated_run_with_patches(self): - # We can call the same function with run_with_patches more than - # once. All patches apply for each call. - def f(): - return (self.test_object.foo, self.test_object.bar, - self.test_object.baz) - - self.monkey_patcher.add_patch(self.test_object, 'foo', 'haha') - result = self.monkey_patcher.run_with_patches(f) - self.assertEquals( - ('haha', self.original_object.bar, self.original_object.baz), - result) - result = self.monkey_patcher.run_with_patches(f) - self.assertEquals( - ('haha', self.original_object.bar, self.original_object.baz), - result) - - def test_run_with_patches_restores(self): - # run_with_patches restores the original values after the function has - # executed. - self.monkey_patcher.add_patch(self.test_object, 'foo', 'haha') - self.assertEquals(self.original_object.foo, self.test_object.foo) - self.monkey_patcher.run_with_patches(lambda: None) - self.assertEquals(self.original_object.foo, self.test_object.foo) - - def test_run_with_patches_restores_on_exception(self): - # run_with_patches restores the original values even when the function - # raises an exception. - def _(): - self.assertEquals(self.test_object.foo, 'haha') - self.assertEquals(self.test_object.bar, 'blahblah') - raise RuntimeError("Something went wrong!") - - self.monkey_patcher.add_patch(self.test_object, 'foo', 'haha') - self.monkey_patcher.add_patch(self.test_object, 'bar', 'blahblah') - - self.assertThat(lambda:self.monkey_patcher.run_with_patches(_), - Raises(MatchesException(RuntimeError("Something went wrong!")))) - self.assertEquals(self.test_object.foo, self.original_object.foo) - self.assertEquals(self.test_object.bar, self.original_object.bar) - - -class TestPatchHelper(TestCase): - - def test_patch_patches(self): - # patch(obj, name, value) sets obj.name to value. - test_object = TestObj() - patch(test_object, 'foo', 42) - self.assertEqual(42, test_object.foo) - - def test_patch_returns_cleanup(self): - # patch(obj, name, value) returns a nullary callable that restores obj - # to its original state when run. - test_object = TestObj() - original = test_object.foo - cleanup = patch(test_object, 'foo', 42) - cleanup() - self.assertEqual(original, test_object.foo) - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/test_run.py b/lib/testtools/testtools/tests/test_run.py deleted file mode 100644 index 5971a4be44b..00000000000 --- a/lib/testtools/testtools/tests/test_run.py +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright (c) 2010 testtools developers. See LICENSE for details. - -"""Tests for the test runner logic.""" - -from unittest import TestSuite - -from testtools.compat import ( - _b, - StringIO, - ) -from testtools.helpers import try_import -fixtures = try_import('fixtures') - -import testtools -from testtools import TestCase, run -from testtools.matchers import Contains - - -if fixtures: - class SampleTestFixture(fixtures.Fixture): - """Creates testtools.runexample temporarily.""" - - def __init__(self): - self.package = fixtures.PythonPackage( - 'runexample', [('__init__.py', _b(""" -from testtools import TestCase - -class TestFoo(TestCase): - def test_bar(self): - pass - def test_quux(self): - pass -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) -"""))]) - - def setUp(self): - super(SampleTestFixture, self).setUp() - self.useFixture(self.package) - testtools.__path__.append(self.package.base) - self.addCleanup(testtools.__path__.remove, self.package.base) - - -class TestRun(TestCase): - - def setUp(self): - super(TestRun, self).setUp() - if fixtures is None: - self.skipTest("Need fixtures") - - def test_run_list(self): - self.useFixture(SampleTestFixture()) - out = StringIO() - run.main(['prog', '-l', 'testtools.runexample.test_suite'], out) - self.assertEqual("""testtools.runexample.TestFoo.test_bar -testtools.runexample.TestFoo.test_quux -""", out.getvalue()) - - def test_run_orders_tests(self): - self.useFixture(SampleTestFixture()) - out = StringIO() - # We load two tests - one that exists and one that doesn't, and we - # should get the one that exists and neither the one that doesn't nor - # the unmentioned one that does. - tempdir = self.useFixture(fixtures.TempDir()) - tempname = tempdir.path + '/tests.list' - f = open(tempname, 'wb') - try: - f.write(_b(""" -testtools.runexample.TestFoo.test_bar -testtools.runexample.missingtest -""")) - finally: - f.close() - run.main(['prog', '-l', '--load-list', tempname, - 'testtools.runexample.test_suite'], out) - self.assertEqual("""testtools.runexample.TestFoo.test_bar -""", out.getvalue()) - - def test_run_load_list(self): - self.useFixture(SampleTestFixture()) - out = StringIO() - # We load two tests - one that exists and one that doesn't, and we - # should get the one that exists and neither the one that doesn't nor - # the unmentioned one that does. - tempdir = self.useFixture(fixtures.TempDir()) - tempname = tempdir.path + '/tests.list' - f = open(tempname, 'wb') - try: - f.write(_b(""" -testtools.runexample.TestFoo.test_bar -testtools.runexample.missingtest -""")) - finally: - f.close() - run.main(['prog', '-l', '--load-list', tempname, - 'testtools.runexample.test_suite'], out) - self.assertEqual("""testtools.runexample.TestFoo.test_bar -""", out.getvalue()) - - def test_run_failfast(self): - stdout = self.useFixture(fixtures.StringStream('stdout')) - - class Failing(TestCase): - def test_a(self): - self.fail('a') - def test_b(self): - self.fail('b') - runner = run.TestToolsTestRunner(failfast=True) - with fixtures.MonkeyPatch('sys.stdout', stdout.stream): - runner.run(TestSuite([Failing('test_a'), Failing('test_b')])) - self.assertThat( - stdout.getDetails()['stdout'].as_text(), Contains('Ran 1 test')) - - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/test_runtest.py b/lib/testtools/testtools/tests/test_runtest.py deleted file mode 100644 index afbb8baf395..00000000000 --- a/lib/testtools/testtools/tests/test_runtest.py +++ /dev/null @@ -1,303 +0,0 @@ -# Copyright (c) 2009-2011 testtools developers. See LICENSE for details. - -"""Tests for the RunTest single test execution logic.""" - -from testtools import ( - ExtendedToOriginalDecorator, - run_test_with, - RunTest, - TestCase, - TestResult, - ) -from testtools.matchers import MatchesException, Is, Raises -from testtools.testresult.doubles import ExtendedTestResult -from testtools.tests.helpers import FullStackRunTest - - -class TestRunTest(TestCase): - - run_tests_with = FullStackRunTest - - def make_case(self): - class Case(TestCase): - def test(self): - pass - return Case('test') - - def test___init___short(self): - run = RunTest("bar") - self.assertEqual("bar", run.case) - self.assertEqual([], run.handlers) - - def test__init____handlers(self): - handlers = [("quux", "baz")] - run = RunTest("bar", handlers) - self.assertEqual(handlers, run.handlers) - - def test_run_with_result(self): - # test.run passes result down to _run_test_method. - log = [] - class Case(TestCase): - def _run_test_method(self, result): - log.append(result) - case = Case('_run_test_method') - run = RunTest(case, lambda x: log.append(x)) - result = TestResult() - run.run(result) - self.assertEqual(1, len(log)) - self.assertEqual(result, log[0].decorated) - - def test_run_no_result_manages_new_result(self): - log = [] - run = RunTest(self.make_case(), lambda x: log.append(x) or x) - result = run.run() - self.assertIsInstance(result.decorated, TestResult) - - def test__run_core_called(self): - case = self.make_case() - log = [] - run = RunTest(case, lambda x: x) - run._run_core = lambda: log.append('foo') - run.run() - self.assertEqual(['foo'], log) - - def test__run_user_does_not_catch_keyboard(self): - case = self.make_case() - def raises(): - raise KeyboardInterrupt("yo") - run = RunTest(case, None) - run.result = ExtendedTestResult() - self.assertThat(lambda: run._run_user(raises), - Raises(MatchesException(KeyboardInterrupt))) - self.assertEqual([], run.result._events) - - def test__run_user_calls_onException(self): - case = self.make_case() - log = [] - def handler(exc_info): - log.append("got it") - self.assertEqual(3, len(exc_info)) - self.assertIsInstance(exc_info[1], KeyError) - self.assertIs(KeyError, exc_info[0]) - case.addOnException(handler) - e = KeyError('Yo') - def raises(): - raise e - run = RunTest(case, [(KeyError, None)]) - run.result = ExtendedTestResult() - status = run._run_user(raises) - self.assertEqual(run.exception_caught, status) - self.assertEqual([], run.result._events) - self.assertEqual(["got it"], log) - - def test__run_user_can_catch_Exception(self): - case = self.make_case() - e = Exception('Yo') - def raises(): - raise e - log = [] - run = RunTest(case, [(Exception, None)]) - run.result = ExtendedTestResult() - status = run._run_user(raises) - self.assertEqual(run.exception_caught, status) - self.assertEqual([], run.result._events) - self.assertEqual([], log) - - def test__run_user_uncaught_Exception_raised(self): - case = self.make_case() - e = KeyError('Yo') - def raises(): - raise e - log = [] - def log_exc(self, result, err): - log.append((result, err)) - run = RunTest(case, [(ValueError, log_exc)]) - run.result = ExtendedTestResult() - self.assertThat(lambda: run._run_user(raises), - Raises(MatchesException(KeyError))) - self.assertEqual([], run.result._events) - self.assertEqual([], log) - - def test__run_user_uncaught_Exception_from_exception_handler_raised(self): - case = self.make_case() - def broken_handler(exc_info): - # ValueError because thats what we know how to catch - and must - # not. - raise ValueError('boo') - case.addOnException(broken_handler) - e = KeyError('Yo') - def raises(): - raise e - log = [] - def log_exc(self, result, err): - log.append((result, err)) - run = RunTest(case, [(ValueError, log_exc)]) - run.result = ExtendedTestResult() - self.assertThat(lambda: run._run_user(raises), - Raises(MatchesException(ValueError))) - self.assertEqual([], run.result._events) - self.assertEqual([], log) - - def test__run_user_returns_result(self): - case = self.make_case() - def returns(): - return 1 - run = RunTest(case) - run.result = ExtendedTestResult() - self.assertEqual(1, run._run_user(returns)) - self.assertEqual([], run.result._events) - - def test__run_one_decorates_result(self): - log = [] - class Run(RunTest): - def _run_prepared_result(self, result): - log.append(result) - return result - run = Run(self.make_case(), lambda x: x) - result = run._run_one('foo') - self.assertEqual([result], log) - self.assertIsInstance(log[0], ExtendedToOriginalDecorator) - self.assertEqual('foo', result.decorated) - - def test__run_prepared_result_calls_start_and_stop_test(self): - result = ExtendedTestResult() - case = self.make_case() - run = RunTest(case, lambda x: x) - run.run(result) - self.assertEqual([ - ('startTest', case), - ('addSuccess', case), - ('stopTest', case), - ], result._events) - - def test__run_prepared_result_calls_stop_test_always(self): - result = ExtendedTestResult() - case = self.make_case() - def inner(): - raise Exception("foo") - run = RunTest(case, lambda x: x) - run._run_core = inner - self.assertThat(lambda: run.run(result), - Raises(MatchesException(Exception("foo")))) - self.assertEqual([ - ('startTest', case), - ('stopTest', case), - ], result._events) - - -class CustomRunTest(RunTest): - - marker = object() - - def run(self, result=None): - return self.marker - - -class TestTestCaseSupportForRunTest(TestCase): - - def test_pass_custom_run_test(self): - class SomeCase(TestCase): - def test_foo(self): - pass - result = TestResult() - case = SomeCase('test_foo', runTest=CustomRunTest) - from_run_test = case.run(result) - self.assertThat(from_run_test, Is(CustomRunTest.marker)) - - def test_default_is_runTest_class_variable(self): - class SomeCase(TestCase): - run_tests_with = CustomRunTest - def test_foo(self): - pass - result = TestResult() - case = SomeCase('test_foo') - from_run_test = case.run(result) - self.assertThat(from_run_test, Is(CustomRunTest.marker)) - - def test_constructor_argument_overrides_class_variable(self): - # If a 'runTest' argument is passed to the test's constructor, that - # overrides the class variable. - marker = object() - class DifferentRunTest(RunTest): - def run(self, result=None): - return marker - class SomeCase(TestCase): - run_tests_with = CustomRunTest - def test_foo(self): - pass - result = TestResult() - case = SomeCase('test_foo', runTest=DifferentRunTest) - from_run_test = case.run(result) - self.assertThat(from_run_test, Is(marker)) - - def test_decorator_for_run_test(self): - # Individual test methods can be marked as needing a special runner. - class SomeCase(TestCase): - @run_test_with(CustomRunTest) - def test_foo(self): - pass - result = TestResult() - case = SomeCase('test_foo') - from_run_test = case.run(result) - self.assertThat(from_run_test, Is(CustomRunTest.marker)) - - def test_extended_decorator_for_run_test(self): - # Individual test methods can be marked as needing a special runner. - # Extra arguments can be passed to the decorator which will then be - # passed on to the RunTest object. - marker = object() - class FooRunTest(RunTest): - def __init__(self, case, handlers=None, bar=None): - super(FooRunTest, self).__init__(case, handlers) - self.bar = bar - def run(self, result=None): - return self.bar - class SomeCase(TestCase): - @run_test_with(FooRunTest, bar=marker) - def test_foo(self): - pass - result = TestResult() - case = SomeCase('test_foo') - from_run_test = case.run(result) - self.assertThat(from_run_test, Is(marker)) - - def test_works_as_inner_decorator(self): - # Even if run_test_with is the innermost decorator, it will be - # respected. - def wrapped(function): - """Silly, trivial decorator.""" - def decorated(*args, **kwargs): - return function(*args, **kwargs) - decorated.__name__ = function.__name__ - decorated.__dict__.update(function.__dict__) - return decorated - class SomeCase(TestCase): - @wrapped - @run_test_with(CustomRunTest) - def test_foo(self): - pass - result = TestResult() - case = SomeCase('test_foo') - from_run_test = case.run(result) - self.assertThat(from_run_test, Is(CustomRunTest.marker)) - - def test_constructor_overrides_decorator(self): - # If a 'runTest' argument is passed to the test's constructor, that - # overrides the decorator. - marker = object() - class DifferentRunTest(RunTest): - def run(self, result=None): - return marker - class SomeCase(TestCase): - @run_test_with(CustomRunTest) - def test_foo(self): - pass - result = TestResult() - case = SomeCase('test_foo', runTest=DifferentRunTest) - from_run_test = case.run(result) - self.assertThat(from_run_test, Is(marker)) - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/test_spinner.py b/lib/testtools/testtools/tests/test_spinner.py deleted file mode 100644 index 3d677bd7545..00000000000 --- a/lib/testtools/testtools/tests/test_spinner.py +++ /dev/null @@ -1,332 +0,0 @@ -# Copyright (c) 2010 testtools developers. See LICENSE for details. - -"""Tests for the evil Twisted reactor-spinning we do.""" - -import os -import signal - -from testtools import ( - skipIf, - TestCase, - ) -from testtools.helpers import try_import -from testtools.matchers import ( - Equals, - Is, - MatchesException, - Raises, - ) - -_spinner = try_import('testtools._spinner') - -defer = try_import('twisted.internet.defer') -Failure = try_import('twisted.python.failure.Failure') - - -class NeedsTwistedTestCase(TestCase): - - def setUp(self): - super(NeedsTwistedTestCase, self).setUp() - if defer is None or Failure is None: - self.skipTest("Need Twisted to run") - - -class TestNotReentrant(NeedsTwistedTestCase): - - def test_not_reentrant(self): - # A function decorated as not being re-entrant will raise a - # _spinner.ReentryError if it is called while it is running. - calls = [] - @_spinner.not_reentrant - def log_something(): - calls.append(None) - if len(calls) < 5: - log_something() - self.assertThat( - log_something, Raises(MatchesException(_spinner.ReentryError))) - self.assertEqual(1, len(calls)) - - def test_deeper_stack(self): - calls = [] - @_spinner.not_reentrant - def g(): - calls.append(None) - if len(calls) < 5: - f() - @_spinner.not_reentrant - def f(): - calls.append(None) - if len(calls) < 5: - g() - self.assertThat(f, Raises(MatchesException(_spinner.ReentryError))) - self.assertEqual(2, len(calls)) - - -class TestExtractResult(NeedsTwistedTestCase): - - def test_not_fired(self): - # _spinner.extract_result raises _spinner.DeferredNotFired if it's - # given a Deferred that has not fired. - self.assertThat(lambda:_spinner.extract_result(defer.Deferred()), - Raises(MatchesException(_spinner.DeferredNotFired))) - - def test_success(self): - # _spinner.extract_result returns the value of the Deferred if it has - # fired successfully. - marker = object() - d = defer.succeed(marker) - self.assertThat(_spinner.extract_result(d), Equals(marker)) - - def test_failure(self): - # _spinner.extract_result raises the failure's exception if it's given - # a Deferred that is failing. - try: - 1/0 - except ZeroDivisionError: - f = Failure() - d = defer.fail(f) - self.assertThat(lambda:_spinner.extract_result(d), - Raises(MatchesException(ZeroDivisionError))) - - -class TestTrapUnhandledErrors(NeedsTwistedTestCase): - - def test_no_deferreds(self): - marker = object() - result, errors = _spinner.trap_unhandled_errors(lambda: marker) - self.assertEqual([], errors) - self.assertIs(marker, result) - - def test_unhandled_error(self): - failures = [] - def make_deferred_but_dont_handle(): - try: - 1/0 - except ZeroDivisionError: - f = Failure() - failures.append(f) - defer.fail(f) - result, errors = _spinner.trap_unhandled_errors( - make_deferred_but_dont_handle) - self.assertIs(None, result) - self.assertEqual(failures, [error.failResult for error in errors]) - - -class TestRunInReactor(NeedsTwistedTestCase): - - def make_reactor(self): - from twisted.internet import reactor - return reactor - - def make_spinner(self, reactor=None): - if reactor is None: - reactor = self.make_reactor() - return _spinner.Spinner(reactor) - - def make_timeout(self): - return 0.01 - - def test_function_called(self): - # run_in_reactor actually calls the function given to it. - calls = [] - marker = object() - self.make_spinner().run(self.make_timeout(), calls.append, marker) - self.assertThat(calls, Equals([marker])) - - def test_return_value_returned(self): - # run_in_reactor returns the value returned by the function given to - # it. - marker = object() - result = self.make_spinner().run(self.make_timeout(), lambda: marker) - self.assertThat(result, Is(marker)) - - def test_exception_reraised(self): - # If the given function raises an error, run_in_reactor re-raises that - # error. - self.assertThat( - lambda:self.make_spinner().run(self.make_timeout(), lambda: 1/0), - Raises(MatchesException(ZeroDivisionError))) - - def test_keyword_arguments(self): - # run_in_reactor passes keyword arguments on. - calls = [] - function = lambda *a, **kw: calls.extend([a, kw]) - self.make_spinner().run(self.make_timeout(), function, foo=42) - self.assertThat(calls, Equals([(), {'foo': 42}])) - - def test_not_reentrant(self): - # run_in_reactor raises an error if it is called inside another call - # to run_in_reactor. - spinner = self.make_spinner() - self.assertThat(lambda: spinner.run( - self.make_timeout(), spinner.run, self.make_timeout(), - lambda: None), Raises(MatchesException(_spinner.ReentryError))) - - def test_deferred_value_returned(self): - # If the given function returns a Deferred, run_in_reactor returns the - # value in the Deferred at the end of the callback chain. - marker = object() - result = self.make_spinner().run( - self.make_timeout(), lambda: defer.succeed(marker)) - self.assertThat(result, Is(marker)) - - def test_preserve_signal_handler(self): - signals = ['SIGINT', 'SIGTERM', 'SIGCHLD'] - signals = filter( - None, (getattr(signal, name, None) for name in signals)) - for sig in signals: - self.addCleanup(signal.signal, sig, signal.getsignal(sig)) - new_hdlrs = list(lambda *a: None for _ in signals) - for sig, hdlr in zip(signals, new_hdlrs): - signal.signal(sig, hdlr) - spinner = self.make_spinner() - spinner.run(self.make_timeout(), lambda: None) - self.assertEqual(new_hdlrs, map(signal.getsignal, signals)) - - def test_timeout(self): - # If the function takes too long to run, we raise a - # _spinner.TimeoutError. - timeout = self.make_timeout() - self.assertThat( - lambda:self.make_spinner().run(timeout, lambda: defer.Deferred()), - Raises(MatchesException(_spinner.TimeoutError))) - - def test_no_junk_by_default(self): - # If the reactor hasn't spun yet, then there cannot be any junk. - spinner = self.make_spinner() - self.assertThat(spinner.get_junk(), Equals([])) - - def test_clean_do_nothing(self): - # If there's nothing going on in the reactor, then clean does nothing - # and returns an empty list. - spinner = self.make_spinner() - result = spinner._clean() - self.assertThat(result, Equals([])) - - def test_clean_delayed_call(self): - # If there's a delayed call in the reactor, then clean cancels it and - # returns an empty list. - reactor = self.make_reactor() - spinner = self.make_spinner(reactor) - call = reactor.callLater(10, lambda: None) - results = spinner._clean() - self.assertThat(results, Equals([call])) - self.assertThat(call.active(), Equals(False)) - - def test_clean_delayed_call_cancelled(self): - # If there's a delayed call that's just been cancelled, then it's no - # longer there. - reactor = self.make_reactor() - spinner = self.make_spinner(reactor) - call = reactor.callLater(10, lambda: None) - call.cancel() - results = spinner._clean() - self.assertThat(results, Equals([])) - - def test_clean_selectables(self): - # If there's still a selectable (e.g. a listening socket), then - # clean() removes it from the reactor's registry. - # - # Note that the socket is left open. This emulates a bug in trial. - from twisted.internet.protocol import ServerFactory - reactor = self.make_reactor() - spinner = self.make_spinner(reactor) - port = reactor.listenTCP(0, ServerFactory()) - spinner.run(self.make_timeout(), lambda: None) - results = spinner.get_junk() - self.assertThat(results, Equals([port])) - - def test_clean_running_threads(self): - import threading - import time - current_threads = list(threading.enumerate()) - reactor = self.make_reactor() - timeout = self.make_timeout() - spinner = self.make_spinner(reactor) - spinner.run(timeout, reactor.callInThread, time.sleep, timeout / 2.0) - # Python before 2.5 has a race condition with thread handling where - # join() does not remove threads from enumerate before returning - the - # thread being joined does the removal. This was fixed in Python 2.5 - # but we still support 2.4, so we have to workaround the issue. - # http://bugs.python.org/issue1703448. - self.assertThat( - [thread for thread in threading.enumerate() if thread.isAlive()], - Equals(current_threads)) - - def test_leftover_junk_available(self): - # If 'run' is given a function that leaves the reactor dirty in some - # way, 'run' will clean up the reactor and then store information - # about the junk. This information can be got using get_junk. - from twisted.internet.protocol import ServerFactory - reactor = self.make_reactor() - spinner = self.make_spinner(reactor) - port = spinner.run( - self.make_timeout(), reactor.listenTCP, 0, ServerFactory()) - self.assertThat(spinner.get_junk(), Equals([port])) - - def test_will_not_run_with_previous_junk(self): - # If 'run' is called and there's still junk in the spinner's junk - # list, then the spinner will refuse to run. - from twisted.internet.protocol import ServerFactory - reactor = self.make_reactor() - spinner = self.make_spinner(reactor) - timeout = self.make_timeout() - spinner.run(timeout, reactor.listenTCP, 0, ServerFactory()) - self.assertThat(lambda: spinner.run(timeout, lambda: None), - Raises(MatchesException(_spinner.StaleJunkError))) - - def test_clear_junk_clears_previous_junk(self): - # If 'run' is called and there's still junk in the spinner's junk - # list, then the spinner will refuse to run. - from twisted.internet.protocol import ServerFactory - reactor = self.make_reactor() - spinner = self.make_spinner(reactor) - timeout = self.make_timeout() - port = spinner.run(timeout, reactor.listenTCP, 0, ServerFactory()) - junk = spinner.clear_junk() - self.assertThat(junk, Equals([port])) - self.assertThat(spinner.get_junk(), Equals([])) - - @skipIf(os.name != "posix", "Sending SIGINT with os.kill is posix only") - def test_sigint_raises_no_result_error(self): - # If we get a SIGINT during a run, we raise _spinner.NoResultError. - SIGINT = getattr(signal, 'SIGINT', None) - if not SIGINT: - self.skipTest("SIGINT not available") - reactor = self.make_reactor() - spinner = self.make_spinner(reactor) - timeout = self.make_timeout() - reactor.callLater(timeout, os.kill, os.getpid(), SIGINT) - self.assertThat(lambda:spinner.run(timeout * 5, defer.Deferred), - Raises(MatchesException(_spinner.NoResultError))) - self.assertEqual([], spinner._clean()) - - @skipIf(os.name != "posix", "Sending SIGINT with os.kill is posix only") - def test_sigint_raises_no_result_error_second_time(self): - # If we get a SIGINT during a run, we raise _spinner.NoResultError. - # This test is exactly the same as test_sigint_raises_no_result_error, - # and exists to make sure we haven't futzed with state. - self.test_sigint_raises_no_result_error() - - @skipIf(os.name != "posix", "Sending SIGINT with os.kill is posix only") - def test_fast_sigint_raises_no_result_error(self): - # If we get a SIGINT during a run, we raise _spinner.NoResultError. - SIGINT = getattr(signal, 'SIGINT', None) - if not SIGINT: - self.skipTest("SIGINT not available") - reactor = self.make_reactor() - spinner = self.make_spinner(reactor) - timeout = self.make_timeout() - reactor.callWhenRunning(os.kill, os.getpid(), SIGINT) - self.assertThat(lambda:spinner.run(timeout * 5, defer.Deferred), - Raises(MatchesException(_spinner.NoResultError))) - self.assertEqual([], spinner._clean()) - - @skipIf(os.name != "posix", "Sending SIGINT with os.kill is posix only") - def test_fast_sigint_raises_no_result_error_second_time(self): - self.test_fast_sigint_raises_no_result_error() - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/test_tags.py b/lib/testtools/testtools/tests/test_tags.py deleted file mode 100644 index 5010f9ac12c..00000000000 --- a/lib/testtools/testtools/tests/test_tags.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright (c) 2012 testtools developers. See LICENSE for details. - -"""Test tag support.""" - - -from testtools import TestCase -from testtools.tags import TagContext - - -class TestTags(TestCase): - - def test_no_tags(self): - # A tag context has no tags initially. - tag_context = TagContext() - self.assertEqual(set(), tag_context.get_current_tags()) - - def test_add_tag(self): - # A tag added with change_tags appears in get_current_tags. - tag_context = TagContext() - tag_context.change_tags(set(['foo']), set()) - self.assertEqual(set(['foo']), tag_context.get_current_tags()) - - def test_add_tag_twice(self): - # Calling change_tags twice to add tags adds both tags to the current - # tags. - tag_context = TagContext() - tag_context.change_tags(set(['foo']), set()) - tag_context.change_tags(set(['bar']), set()) - self.assertEqual( - set(['foo', 'bar']), tag_context.get_current_tags()) - - def test_change_tags_returns_tags(self): - # change_tags returns the current tags. This is a convenience. - tag_context = TagContext() - tags = tag_context.change_tags(set(['foo']), set()) - self.assertEqual(set(['foo']), tags) - - def test_remove_tag(self): - # change_tags can remove tags from the context. - tag_context = TagContext() - tag_context.change_tags(set(['foo']), set()) - tag_context.change_tags(set(), set(['foo'])) - self.assertEqual(set(), tag_context.get_current_tags()) - - def test_child_context(self): - # A TagContext can have a parent. If so, its tags are the tags of the - # parent at the moment of construction. - parent = TagContext() - parent.change_tags(set(['foo']), set()) - child = TagContext(parent) - self.assertEqual( - parent.get_current_tags(), child.get_current_tags()) - - def test_add_to_child(self): - # Adding a tag to the child context doesn't affect the parent. - parent = TagContext() - parent.change_tags(set(['foo']), set()) - child = TagContext(parent) - child.change_tags(set(['bar']), set()) - self.assertEqual(set(['foo', 'bar']), child.get_current_tags()) - self.assertEqual(set(['foo']), parent.get_current_tags()) - - def test_remove_in_child(self): - # A tag that was in the parent context can be removed from the child - # context without affect the parent. - parent = TagContext() - parent.change_tags(set(['foo']), set()) - child = TagContext(parent) - child.change_tags(set(), set(['foo'])) - self.assertEqual(set(), child.get_current_tags()) - self.assertEqual(set(['foo']), parent.get_current_tags()) - - def test_parent(self): - # The parent can be retrieved from a child context. - parent = TagContext() - parent.change_tags(set(['foo']), set()) - child = TagContext(parent) - child.change_tags(set(), set(['foo'])) - self.assertEqual(parent, child.parent) - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/test_testcase.py b/lib/testtools/testtools/tests/test_testcase.py deleted file mode 100644 index eca781bde73..00000000000 --- a/lib/testtools/testtools/tests/test_testcase.py +++ /dev/null @@ -1,1335 +0,0 @@ -# Copyright (c) 2008-2012 testtools developers. See LICENSE for details. - -"""Tests for extensions to the base test library.""" - -from doctest import ELLIPSIS -from pprint import pformat -import sys -import unittest - -from testtools import ( - ErrorHolder, - MultipleExceptions, - PlaceHolder, - TestCase, - clone_test_with_new_id, - content, - skip, - skipIf, - skipUnless, - testcase, - ) -from testtools.compat import ( - _b, - _u, - ) -from testtools.content import TracebackContent -from testtools.matchers import ( - Annotate, - DocTestMatches, - Equals, - MatchesException, - Raises, - ) -from testtools.testcase import Nullary -from testtools.testresult.doubles import ( - Python26TestResult, - Python27TestResult, - ExtendedTestResult, - ) -from testtools.tests.helpers import ( - an_exc_info, - FullStackRunTest, - LoggingResult, - ) -try: - exec('from __future__ import with_statement') -except SyntaxError: - pass -else: - from testtools.tests.test_with_with import * - - -class TestPlaceHolder(TestCase): - - run_test_with = FullStackRunTest - - def makePlaceHolder(self, test_id="foo", short_description=None): - return PlaceHolder(test_id, short_description) - - def test_id_comes_from_constructor(self): - # The id() of a PlaceHolder is whatever you pass into the constructor. - test = PlaceHolder("test id") - self.assertEqual("test id", test.id()) - - def test_shortDescription_is_id(self): - # The shortDescription() of a PlaceHolder is the id, by default. - test = PlaceHolder("test id") - self.assertEqual(test.id(), test.shortDescription()) - - def test_shortDescription_specified(self): - # If a shortDescription is provided to the constructor, then - # shortDescription() returns that instead. - test = PlaceHolder("test id", "description") - self.assertEqual("description", test.shortDescription()) - - def test_repr_just_id(self): - # repr(placeholder) shows you how the object was constructed. - test = PlaceHolder("test id") - self.assertEqual( - "" % repr( - test.id()), repr(test)) - - def test_repr_with_description(self): - # repr(placeholder) shows you how the object was constructed. - test = PlaceHolder("test id", "description") - self.assertEqual( - "" % ( - test.id(), test.shortDescription()), repr(test)) - - def test_repr_custom_outcome(self): - test = PlaceHolder("test id", outcome='addSkip') - self.assertEqual( - "" % ( - test.id()), repr(test)) - - def test_counts_as_one_test(self): - # A placeholder test counts as one test. - test = self.makePlaceHolder() - self.assertEqual(1, test.countTestCases()) - - def test_str_is_id(self): - # str(placeholder) is always the id(). We are not barbarians. - test = self.makePlaceHolder() - self.assertEqual(test.id(), str(test)) - - def test_runs_as_success(self): - # When run, a PlaceHolder test records a success. - test = self.makePlaceHolder() - log = [] - test.run(LoggingResult(log)) - self.assertEqual( - [('startTest', test), ('addSuccess', test), ('stopTest', test)], - log) - - def test_supplies_details(self): - details = {'quux':None} - test = PlaceHolder('foo', details=details) - result = ExtendedTestResult() - test.run(result) - self.assertEqual( - [('startTest', test), - ('addSuccess', test, details), - ('stopTest', test)], - result._events) - - def test_call_is_run(self): - # A PlaceHolder can be called, in which case it behaves like run. - test = self.makePlaceHolder() - run_log = [] - test.run(LoggingResult(run_log)) - call_log = [] - test(LoggingResult(call_log)) - self.assertEqual(run_log, call_log) - - def test_runs_without_result(self): - # A PlaceHolder can be run without a result, in which case there's no - # way to actually get at the result. - self.makePlaceHolder().run() - - def test_debug(self): - # A PlaceHolder can be debugged. - self.makePlaceHolder().debug() - - -class TestErrorHolder(TestCase): - # Note that these tests exist because ErrorHolder exists - it could be - # deprecated and dropped at this point. - - run_test_with = FullStackRunTest - - def makeException(self): - try: - raise RuntimeError("danger danger") - except: - return sys.exc_info() - - def makePlaceHolder(self, test_id="foo", error=None, - short_description=None): - if error is None: - error = self.makeException() - return ErrorHolder(test_id, error, short_description) - - def test_id_comes_from_constructor(self): - # The id() of a PlaceHolder is whatever you pass into the constructor. - test = ErrorHolder("test id", self.makeException()) - self.assertEqual("test id", test.id()) - - def test_shortDescription_is_id(self): - # The shortDescription() of a PlaceHolder is the id, by default. - test = ErrorHolder("test id", self.makeException()) - self.assertEqual(test.id(), test.shortDescription()) - - def test_shortDescription_specified(self): - # If a shortDescription is provided to the constructor, then - # shortDescription() returns that instead. - test = ErrorHolder("test id", self.makeException(), "description") - self.assertEqual("description", test.shortDescription()) - - def test_counts_as_one_test(self): - # A placeholder test counts as one test. - test = self.makePlaceHolder() - self.assertEqual(1, test.countTestCases()) - - def test_str_is_id(self): - # str(placeholder) is always the id(). We are not barbarians. - test = self.makePlaceHolder() - self.assertEqual(test.id(), str(test)) - - def test_runs_as_error(self): - # When run, an ErrorHolder test records an error. - error = self.makeException() - test = self.makePlaceHolder(error=error) - result = ExtendedTestResult() - log = result._events - test.run(result) - self.assertEqual( - [('startTest', test), - ('addError', test, test._details), - ('stopTest', test)], log) - - def test_call_is_run(self): - # A PlaceHolder can be called, in which case it behaves like run. - test = self.makePlaceHolder() - run_log = [] - test.run(LoggingResult(run_log)) - call_log = [] - test(LoggingResult(call_log)) - self.assertEqual(run_log, call_log) - - def test_runs_without_result(self): - # A PlaceHolder can be run without a result, in which case there's no - # way to actually get at the result. - self.makePlaceHolder().run() - - def test_debug(self): - # A PlaceHolder can be debugged. - self.makePlaceHolder().debug() - - -class TestEquality(TestCase): - """Test ``TestCase``'s equality implementation.""" - - run_test_with = FullStackRunTest - - def test_identicalIsEqual(self): - # TestCase's are equal if they are identical. - self.assertEqual(self, self) - - def test_nonIdenticalInUnequal(self): - # TestCase's are not equal if they are not identical. - self.assertNotEqual(TestCase(methodName='run'), - TestCase(methodName='skip')) - - -class TestAssertions(TestCase): - """Test assertions in TestCase.""" - - run_test_with = FullStackRunTest - - def raiseError(self, exceptionFactory, *args, **kwargs): - raise exceptionFactory(*args, **kwargs) - - def test_formatTypes_single(self): - # Given a single class, _formatTypes returns the name. - class Foo(object): - pass - self.assertEqual('Foo', self._formatTypes(Foo)) - - def test_formatTypes_multiple(self): - # Given multiple types, _formatTypes returns the names joined by - # commas. - class Foo(object): - pass - class Bar(object): - pass - self.assertEqual('Foo, Bar', self._formatTypes([Foo, Bar])) - - def test_assertRaises(self): - # assertRaises asserts that a callable raises a particular exception. - self.assertRaises(RuntimeError, self.raiseError, RuntimeError) - - def test_assertRaises_fails_when_no_error_raised(self): - # assertRaises raises self.failureException when it's passed a - # callable that raises no error. - ret = ('orange', 42) - self.assertFails( - " at ...> returned ('orange', 42)", - self.assertRaises, RuntimeError, lambda: ret) - - def test_assertRaises_fails_when_different_error_raised(self): - # assertRaises re-raises an exception that it didn't expect. - self.assertThat(lambda: self.assertRaises(RuntimeError, - self.raiseError, ZeroDivisionError), - Raises(MatchesException(ZeroDivisionError))) - - def test_assertRaises_returns_the_raised_exception(self): - # assertRaises returns the exception object that was raised. This is - # useful for testing that exceptions have the right message. - - # This contraption stores the raised exception, so we can compare it - # to the return value of assertRaises. - raisedExceptions = [] - def raiseError(): - try: - raise RuntimeError('Deliberate error') - except RuntimeError: - raisedExceptions.append(sys.exc_info()[1]) - raise - - exception = self.assertRaises(RuntimeError, raiseError) - self.assertEqual(1, len(raisedExceptions)) - self.assertTrue( - exception is raisedExceptions[0], - "%r is not %r" % (exception, raisedExceptions[0])) - - def test_assertRaises_with_multiple_exceptions(self): - # assertRaises((ExceptionOne, ExceptionTwo), function) asserts that - # function raises one of ExceptionTwo or ExceptionOne. - expectedExceptions = (RuntimeError, ZeroDivisionError) - self.assertRaises( - expectedExceptions, self.raiseError, expectedExceptions[0]) - self.assertRaises( - expectedExceptions, self.raiseError, expectedExceptions[1]) - - def test_assertRaises_with_multiple_exceptions_failure_mode(self): - # If assertRaises is called expecting one of a group of exceptions and - # a callable that doesn't raise an exception, then fail with an - # appropriate error message. - expectedExceptions = (RuntimeError, ZeroDivisionError) - self.assertRaises( - self.failureException, - self.assertRaises, expectedExceptions, lambda: None) - self.assertFails(' at ...> returned None', - self.assertRaises, expectedExceptions, lambda: None) - - def test_assertRaises_function_repr_in_exception(self): - # When assertRaises fails, it includes the repr of the invoked - # function in the error message, so it's easy to locate the problem. - def foo(): - """An arbitrary function.""" - pass - self.assertThat( - lambda: self.assertRaises(Exception, foo), - Raises( - MatchesException(self.failureException, '.*%r.*' % (foo,)))) - - def assertFails(self, message, function, *args, **kwargs): - """Assert that function raises a failure with the given message.""" - failure = self.assertRaises( - self.failureException, function, *args, **kwargs) - self.assertThat(failure, DocTestMatches(message, ELLIPSIS)) - - def test_assertIn_success(self): - # assertIn(needle, haystack) asserts that 'needle' is in 'haystack'. - self.assertIn(3, range(10)) - self.assertIn('foo', 'foo bar baz') - self.assertIn('foo', 'foo bar baz'.split()) - - def test_assertIn_failure(self): - # assertIn(needle, haystack) fails the test when 'needle' is not in - # 'haystack'. - self.assertFails('3 not in [0, 1, 2]', self.assertIn, 3, [0, 1, 2]) - self.assertFails( - '%r not in %r' % ('qux', 'foo bar baz'), - self.assertIn, 'qux', 'foo bar baz') - - def test_assertNotIn_success(self): - # assertNotIn(needle, haystack) asserts that 'needle' is not in - # 'haystack'. - self.assertNotIn(3, [0, 1, 2]) - self.assertNotIn('qux', 'foo bar baz') - - def test_assertNotIn_failure(self): - # assertNotIn(needle, haystack) fails the test when 'needle' is in - # 'haystack'. - self.assertFails('[1, 2, 3] matches Contains(3)', self.assertNotIn, - 3, [1, 2, 3]) - self.assertFails( - "'foo bar baz' matches Contains('foo')", - self.assertNotIn, 'foo', 'foo bar baz') - - def test_assertIsInstance(self): - # assertIsInstance asserts that an object is an instance of a class. - - class Foo(object): - """Simple class for testing assertIsInstance.""" - - foo = Foo() - self.assertIsInstance(foo, Foo) - - def test_assertIsInstance_multiple_classes(self): - # assertIsInstance asserts that an object is an instance of one of a - # group of classes. - - class Foo(object): - """Simple class for testing assertIsInstance.""" - - class Bar(object): - """Another simple class for testing assertIsInstance.""" - - foo = Foo() - self.assertIsInstance(foo, (Foo, Bar)) - self.assertIsInstance(Bar(), (Foo, Bar)) - - def test_assertIsInstance_failure(self): - # assertIsInstance(obj, klass) fails the test when obj is not an - # instance of klass. - - class Foo(object): - """Simple class for testing assertIsInstance.""" - - self.assertFails( - "'42' is not an instance of %s" % self._formatTypes(Foo), - self.assertIsInstance, 42, Foo) - - def test_assertIsInstance_failure_multiple_classes(self): - # assertIsInstance(obj, (klass1, klass2)) fails the test when obj is - # not an instance of klass1 or klass2. - - class Foo(object): - """Simple class for testing assertIsInstance.""" - - class Bar(object): - """Another simple class for testing assertIsInstance.""" - - self.assertFails( - "'42' is not an instance of any of (%s)" % self._formatTypes([Foo, Bar]), - self.assertIsInstance, 42, (Foo, Bar)) - - def test_assertIsInstance_overridden_message(self): - # assertIsInstance(obj, klass, msg) permits a custom message. - self.assertFails("'42' is not an instance of str: foo", - self.assertIsInstance, 42, str, "foo") - - def test_assertIs(self): - # assertIs asserts that an object is identical to another object. - self.assertIs(None, None) - some_list = [42] - self.assertIs(some_list, some_list) - some_object = object() - self.assertIs(some_object, some_object) - - def test_assertIs_fails(self): - # assertIs raises assertion errors if one object is not identical to - # another. - self.assertFails('None is not 42', self.assertIs, None, 42) - self.assertFails('[42] is not [42]', self.assertIs, [42], [42]) - - def test_assertIs_fails_with_message(self): - # assertIs raises assertion errors if one object is not identical to - # another, and includes a user-supplied message, if it's provided. - self.assertFails( - 'None is not 42: foo bar', self.assertIs, None, 42, 'foo bar') - - def test_assertIsNot(self): - # assertIsNot asserts that an object is not identical to another - # object. - self.assertIsNot(None, 42) - self.assertIsNot([42], [42]) - self.assertIsNot(object(), object()) - - def test_assertIsNot_fails(self): - # assertIsNot raises assertion errors if one object is identical to - # another. - self.assertFails('None matches Is(None)', self.assertIsNot, None, None) - some_list = [42] - self.assertFails( - '[42] matches Is([42])', self.assertIsNot, some_list, some_list) - - def test_assertIsNot_fails_with_message(self): - # assertIsNot raises assertion errors if one object is identical to - # another, and includes a user-supplied message if it's provided. - self.assertFails( - 'None matches Is(None): foo bar', self.assertIsNot, None, None, - "foo bar") - - def test_assertThat_matches_clean(self): - class Matcher(object): - def match(self, foo): - return None - self.assertThat("foo", Matcher()) - - def test_assertThat_mismatch_raises_description(self): - calls = [] - class Mismatch(object): - def __init__(self, thing): - self.thing = thing - def describe(self): - calls.append(('describe_diff', self.thing)) - return "object is not a thing" - def get_details(self): - return {} - class Matcher(object): - def match(self, thing): - calls.append(('match', thing)) - return Mismatch(thing) - def __str__(self): - calls.append(('__str__',)) - return "a description" - class Test(TestCase): - def test(self): - self.assertThat("foo", Matcher()) - result = Test("test").run() - self.assertEqual([ - ('match', "foo"), - ('describe_diff', "foo"), - ], calls) - self.assertFalse(result.wasSuccessful()) - - def test_assertThat_output(self): - matchee = 'foo' - matcher = Equals('bar') - expected = matcher.match(matchee).describe() - self.assertFails(expected, self.assertThat, matchee, matcher) - - def test_assertThat_message_is_annotated(self): - matchee = 'foo' - matcher = Equals('bar') - expected = Annotate('woo', matcher).match(matchee).describe() - self.assertFails(expected, self.assertThat, matchee, matcher, 'woo') - - def test_assertThat_verbose_output(self): - matchee = 'foo' - matcher = Equals('bar') - expected = ( - 'Match failed. Matchee: %r\n' - 'Matcher: %s\n' - 'Difference: %s\n' % ( - matchee, - matcher, - matcher.match(matchee).describe(), - )) - self.assertFails( - expected, self.assertThat, matchee, matcher, verbose=True) - - def get_error_string(self, e): - """Get the string showing how 'e' would be formatted in test output. - - This is a little bit hacky, since it's designed to give consistent - output regardless of Python version. - - In testtools, TestResult._exc_info_to_unicode is the point of dispatch - between various different implementations of methods that format - exceptions, so that's what we have to call. However, that method cares - about stack traces and formats the exception class. We don't care - about either of these, so we take its output and parse it a little. - """ - error = TracebackContent((e.__class__, e, None), self).as_text() - # We aren't at all interested in the traceback. - if error.startswith('Traceback (most recent call last):\n'): - lines = error.splitlines(True)[1:] - for i, line in enumerate(lines): - if not line.startswith(' '): - break - error = ''.join(lines[i:]) - # We aren't interested in how the exception type is formatted. - exc_class, error = error.split(': ', 1) - return error - - def test_assertThat_verbose_unicode(self): - # When assertThat is given matchees or matchers that contain non-ASCII - # unicode strings, we can still provide a meaningful error. - matchee = _u('\xa7') - matcher = Equals(_u('a')) - expected = ( - 'Match failed. Matchee: %s\n' - 'Matcher: %s\n' - 'Difference: %s\n\n' % ( - repr(matchee).replace("\\xa7", matchee), - matcher, - matcher.match(matchee).describe(), - )) - e = self.assertRaises( - self.failureException, self.assertThat, matchee, matcher, - verbose=True) - self.assertEqual(expected, self.get_error_string(e)) - - def test_assertEqual_nice_formatting(self): - message = "These things ought not be equal." - a = ['apple', 'banana', 'cherry'] - b = {'Thatcher': 'One who mends roofs of straw', - 'Major': 'A military officer, ranked below colonel', - 'Blair': 'To shout loudly', - 'Brown': 'The colour of healthy human faeces'} - expected_error = '\n'.join([ - '!=:', - 'reference = %s' % pformat(a), - 'actual = %s' % pformat(b), - ': ' + message, - ]) - self.assertFails(expected_error, self.assertEqual, a, b, message) - self.assertFails(expected_error, self.assertEquals, a, b, message) - self.assertFails(expected_error, self.failUnlessEqual, a, b, message) - - def test_assertEqual_formatting_no_message(self): - a = "cat" - b = "dog" - expected_error = "'cat' != 'dog'" - self.assertFails(expected_error, self.assertEqual, a, b) - self.assertFails(expected_error, self.assertEquals, a, b) - self.assertFails(expected_error, self.failUnlessEqual, a, b) - - def test_assertEqual_non_ascii_str_with_newlines(self): - message = _u("Be careful mixing unicode and bytes") - a = "a\n\xa7\n" - b = "Just a longish string so the more verbose output form is used." - expected_error = '\n'.join([ - '!=:', - "reference = '''\\", - 'a', - repr('\xa7')[1:-1], - "'''", - 'actual = %r' % (b,), - ': ' + message, - ]) - self.assertFails(expected_error, self.assertEqual, a, b, message) - - def test_assertIsNone(self): - self.assertIsNone(None) - - expected_error = 'None is not 0' - self.assertFails(expected_error, self.assertIsNone, 0) - - def test_assertIsNotNone(self): - self.assertIsNotNone(0) - self.assertIsNotNone("0") - - expected_error = 'None matches Is(None)' - self.assertFails(expected_error, self.assertIsNotNone, None) - - -class TestAddCleanup(TestCase): - """Tests for TestCase.addCleanup.""" - - run_test_with = FullStackRunTest - - class LoggingTest(TestCase): - """A test that logs calls to setUp, runTest and tearDown.""" - - def setUp(self): - TestCase.setUp(self) - self._calls = ['setUp'] - - def brokenSetUp(self): - # A tearDown that deliberately fails. - self._calls = ['brokenSetUp'] - raise RuntimeError('Deliberate Failure') - - def runTest(self): - self._calls.append('runTest') - - def brokenTest(self): - raise RuntimeError('Deliberate broken test') - - def tearDown(self): - self._calls.append('tearDown') - TestCase.tearDown(self) - - def setUp(self): - TestCase.setUp(self) - self._result_calls = [] - self.test = TestAddCleanup.LoggingTest('runTest') - self.logging_result = LoggingResult(self._result_calls) - - def assertErrorLogEqual(self, messages): - self.assertEqual(messages, [call[0] for call in self._result_calls]) - - def assertTestLogEqual(self, messages): - """Assert that the call log equals 'messages'.""" - case = self._result_calls[0][1] - self.assertEqual(messages, case._calls) - - def logAppender(self, message): - """A cleanup that appends 'message' to the tests log. - - Cleanups are callables that are added to a test by addCleanup. To - verify that our cleanups run in the right order, we add strings to a - list that acts as a log. This method returns a cleanup that will add - the given message to that log when run. - """ - self.test._calls.append(message) - - def test_fixture(self): - # A normal run of self.test logs 'setUp', 'runTest' and 'tearDown'. - # This test doesn't test addCleanup itself, it just sanity checks the - # fixture. - self.test.run(self.logging_result) - self.assertTestLogEqual(['setUp', 'runTest', 'tearDown']) - - def test_cleanup_run_before_tearDown(self): - # Cleanup functions added with 'addCleanup' are called before tearDown - # runs. - self.test.addCleanup(self.logAppender, 'cleanup') - self.test.run(self.logging_result) - self.assertTestLogEqual(['setUp', 'runTest', 'tearDown', 'cleanup']) - - def test_add_cleanup_called_if_setUp_fails(self): - # Cleanup functions added with 'addCleanup' are called even if setUp - # fails. Note that tearDown has a different behavior: it is only - # called when setUp succeeds. - self.test.setUp = self.test.brokenSetUp - self.test.addCleanup(self.logAppender, 'cleanup') - self.test.run(self.logging_result) - self.assertTestLogEqual(['brokenSetUp', 'cleanup']) - - def test_addCleanup_called_in_reverse_order(self): - # Cleanup functions added with 'addCleanup' are called in reverse - # order. - # - # One of the main uses of addCleanup is to dynamically create - # resources that need some sort of explicit tearDown. Often one - # resource will be created in terms of another, e.g., - # self.first = self.makeFirst() - # self.second = self.makeSecond(self.first) - # - # When this happens, we generally want to clean up the second resource - # before the first one, since the second depends on the first. - self.test.addCleanup(self.logAppender, 'first') - self.test.addCleanup(self.logAppender, 'second') - self.test.run(self.logging_result) - self.assertTestLogEqual( - ['setUp', 'runTest', 'tearDown', 'second', 'first']) - - def test_tearDown_runs_after_cleanup_failure(self): - # tearDown runs even if a cleanup function fails. - self.test.addCleanup(lambda: 1/0) - self.test.run(self.logging_result) - self.assertTestLogEqual(['setUp', 'runTest', 'tearDown']) - - def test_cleanups_continue_running_after_error(self): - # All cleanups are always run, even if one or two of them fail. - self.test.addCleanup(self.logAppender, 'first') - self.test.addCleanup(lambda: 1/0) - self.test.addCleanup(self.logAppender, 'second') - self.test.run(self.logging_result) - self.assertTestLogEqual( - ['setUp', 'runTest', 'tearDown', 'second', 'first']) - - def test_error_in_cleanups_are_captured(self): - # If a cleanup raises an error, we want to record it and fail the the - # test, even though we go on to run other cleanups. - self.test.addCleanup(lambda: 1/0) - self.test.run(self.logging_result) - self.assertErrorLogEqual(['startTest', 'addError', 'stopTest']) - - def test_keyboard_interrupt_not_caught(self): - # If a cleanup raises KeyboardInterrupt, it gets reraised. - def raiseKeyboardInterrupt(): - raise KeyboardInterrupt() - self.test.addCleanup(raiseKeyboardInterrupt) - self.assertThat(lambda:self.test.run(self.logging_result), - Raises(MatchesException(KeyboardInterrupt))) - - def test_all_errors_from_MultipleExceptions_reported(self): - # When a MultipleExceptions exception is caught, all the errors are - # reported. - def raiseMany(): - try: - 1/0 - except Exception: - exc_info1 = sys.exc_info() - try: - 1/0 - except Exception: - exc_info2 = sys.exc_info() - raise MultipleExceptions(exc_info1, exc_info2) - self.test.addCleanup(raiseMany) - self.logging_result = ExtendedTestResult() - self.test.run(self.logging_result) - self.assertEqual(['startTest', 'addError', 'stopTest'], - [event[0] for event in self.logging_result._events]) - self.assertEqual(set(['traceback', 'traceback-1']), - set(self.logging_result._events[1][2].keys())) - - def test_multipleCleanupErrorsReported(self): - # Errors from all failing cleanups are reported as separate backtraces. - self.test.addCleanup(lambda: 1/0) - self.test.addCleanup(lambda: 1/0) - self.logging_result = ExtendedTestResult() - self.test.run(self.logging_result) - self.assertEqual(['startTest', 'addError', 'stopTest'], - [event[0] for event in self.logging_result._events]) - self.assertEqual(set(['traceback', 'traceback-1']), - set(self.logging_result._events[1][2].keys())) - - def test_multipleErrorsCoreAndCleanupReported(self): - # Errors from all failing cleanups are reported, with stopTest, - # startTest inserted. - self.test = TestAddCleanup.LoggingTest('brokenTest') - self.test.addCleanup(lambda: 1/0) - self.test.addCleanup(lambda: 1/0) - self.logging_result = ExtendedTestResult() - self.test.run(self.logging_result) - self.assertEqual(['startTest', 'addError', 'stopTest'], - [event[0] for event in self.logging_result._events]) - self.assertEqual(set(['traceback', 'traceback-1', 'traceback-2']), - set(self.logging_result._events[1][2].keys())) - - -class TestWithDetails(TestCase): - - run_test_with = FullStackRunTest - - def assertDetailsProvided(self, case, expected_outcome, expected_keys): - """Assert that when case is run, details are provided to the result. - - :param case: A TestCase to run. - :param expected_outcome: The call that should be made. - :param expected_keys: The keys to look for. - """ - result = ExtendedTestResult() - case.run(result) - case = result._events[0][1] - expected = [ - ('startTest', case), - (expected_outcome, case), - ('stopTest', case), - ] - self.assertEqual(3, len(result._events)) - self.assertEqual(expected[0], result._events[0]) - self.assertEqual(expected[1], result._events[1][0:2]) - # Checking the TB is right is rather tricky. doctest line matching - # would help, but 'meh'. - self.assertEqual(sorted(expected_keys), - sorted(result._events[1][2].keys())) - self.assertEqual(expected[-1], result._events[-1]) - - def get_content(self): - return content.Content( - content.ContentType("text", "foo"), lambda: [_b('foo')]) - - -class TestExpectedFailure(TestWithDetails): - """Tests for expected failures and unexpected successess.""" - - run_test_with = FullStackRunTest - - def make_unexpected_case(self): - class Case(TestCase): - def test(self): - raise testcase._UnexpectedSuccess - case = Case('test') - return case - - def test_raising__UnexpectedSuccess_py27(self): - case = self.make_unexpected_case() - result = Python27TestResult() - case.run(result) - case = result._events[0][1] - self.assertEqual([ - ('startTest', case), - ('addUnexpectedSuccess', case), - ('stopTest', case), - ], result._events) - - def test_raising__UnexpectedSuccess_extended(self): - case = self.make_unexpected_case() - result = ExtendedTestResult() - case.run(result) - case = result._events[0][1] - self.assertEqual([ - ('startTest', case), - ('addUnexpectedSuccess', case, {}), - ('stopTest', case), - ], result._events) - - def make_xfail_case_xfails(self): - content = self.get_content() - class Case(TestCase): - def test(self): - self.addDetail("foo", content) - self.expectFailure("we are sad", self.assertEqual, - 1, 0) - case = Case('test') - return case - - def make_xfail_case_succeeds(self): - content = self.get_content() - class Case(TestCase): - def test(self): - self.addDetail("foo", content) - self.expectFailure("we are sad", self.assertEqual, - 1, 1) - case = Case('test') - return case - - def test_expectFailure_KnownFailure_extended(self): - case = self.make_xfail_case_xfails() - self.assertDetailsProvided(case, "addExpectedFailure", - ["foo", "traceback", "reason"]) - - def test_expectFailure_KnownFailure_unexpected_success(self): - case = self.make_xfail_case_succeeds() - self.assertDetailsProvided(case, "addUnexpectedSuccess", - ["foo", "reason"]) - - -class TestUniqueFactories(TestCase): - """Tests for getUniqueString and getUniqueInteger.""" - - run_test_with = FullStackRunTest - - def test_getUniqueInteger(self): - # getUniqueInteger returns an integer that increments each time you - # call it. - one = self.getUniqueInteger() - self.assertEqual(1, one) - two = self.getUniqueInteger() - self.assertEqual(2, two) - - def test_getUniqueString(self): - # getUniqueString returns the current test id followed by a unique - # integer. - name_one = self.getUniqueString() - self.assertEqual('%s-%d' % (self.id(), 1), name_one) - name_two = self.getUniqueString() - self.assertEqual('%s-%d' % (self.id(), 2), name_two) - - def test_getUniqueString_prefix(self): - # If getUniqueString is given an argument, it uses that argument as - # the prefix of the unique string, rather than the test id. - name_one = self.getUniqueString('foo') - self.assertThat(name_one, Equals('foo-1')) - name_two = self.getUniqueString('bar') - self.assertThat(name_two, Equals('bar-2')) - - -class TestCloneTestWithNewId(TestCase): - """Tests for clone_test_with_new_id.""" - - run_test_with = FullStackRunTest - - def test_clone_test_with_new_id(self): - class FooTestCase(TestCase): - def test_foo(self): - pass - test = FooTestCase('test_foo') - oldName = test.id() - newName = self.getUniqueString() - newTest = clone_test_with_new_id(test, newName) - self.assertEqual(newName, newTest.id()) - self.assertEqual(oldName, test.id(), - "the original test instance should be unchanged.") - - def test_cloned_testcase_does_not_share_details(self): - """A cloned TestCase does not share the details dict.""" - class Test(TestCase): - def test_foo(self): - self.addDetail( - 'foo', content.Content('text/plain', lambda: 'foo')) - orig_test = Test('test_foo') - cloned_test = clone_test_with_new_id(orig_test, self.getUniqueString()) - orig_test.run(unittest.TestResult()) - self.assertEqual('foo', orig_test.getDetails()['foo'].iter_bytes()) - self.assertEqual(None, cloned_test.getDetails().get('foo')) - - -class TestDetailsProvided(TestWithDetails): - - run_test_with = FullStackRunTest - - def test_addDetail(self): - mycontent = self.get_content() - self.addDetail("foo", mycontent) - details = self.getDetails() - self.assertEqual({"foo": mycontent}, details) - - def test_addError(self): - class Case(TestCase): - def test(this): - this.addDetail("foo", self.get_content()) - 1/0 - self.assertDetailsProvided(Case("test"), "addError", - ["foo", "traceback"]) - - def test_addFailure(self): - class Case(TestCase): - def test(this): - this.addDetail("foo", self.get_content()) - self.fail('yo') - self.assertDetailsProvided(Case("test"), "addFailure", - ["foo", "traceback"]) - - def test_addSkip(self): - class Case(TestCase): - def test(this): - this.addDetail("foo", self.get_content()) - self.skip('yo') - self.assertDetailsProvided(Case("test"), "addSkip", - ["foo", "reason"]) - - def test_addSucccess(self): - class Case(TestCase): - def test(this): - this.addDetail("foo", self.get_content()) - self.assertDetailsProvided(Case("test"), "addSuccess", - ["foo"]) - - def test_addUnexpectedSuccess(self): - class Case(TestCase): - def test(this): - this.addDetail("foo", self.get_content()) - raise testcase._UnexpectedSuccess() - self.assertDetailsProvided(Case("test"), "addUnexpectedSuccess", - ["foo"]) - - def test_addDetails_from_Mismatch(self): - content = self.get_content() - class Mismatch(object): - def describe(self): - return "Mismatch" - def get_details(self): - return {"foo": content} - class Matcher(object): - def match(self, thing): - return Mismatch() - def __str__(self): - return "a description" - class Case(TestCase): - def test(self): - self.assertThat("foo", Matcher()) - self.assertDetailsProvided(Case("test"), "addFailure", - ["foo", "traceback"]) - - def test_multiple_addDetails_from_Mismatch(self): - content = self.get_content() - class Mismatch(object): - def describe(self): - return "Mismatch" - def get_details(self): - return {"foo": content, "bar": content} - class Matcher(object): - def match(self, thing): - return Mismatch() - def __str__(self): - return "a description" - class Case(TestCase): - def test(self): - self.assertThat("foo", Matcher()) - self.assertDetailsProvided(Case("test"), "addFailure", - ["bar", "foo", "traceback"]) - - def test_addDetails_with_same_name_as_key_from_get_details(self): - content = self.get_content() - class Mismatch(object): - def describe(self): - return "Mismatch" - def get_details(self): - return {"foo": content} - class Matcher(object): - def match(self, thing): - return Mismatch() - def __str__(self): - return "a description" - class Case(TestCase): - def test(self): - self.addDetail("foo", content) - self.assertThat("foo", Matcher()) - self.assertDetailsProvided(Case("test"), "addFailure", - ["foo", "foo-1", "traceback"]) - - -class TestSetupTearDown(TestCase): - - run_test_with = FullStackRunTest - - def test_setUpNotCalled(self): - class DoesnotcallsetUp(TestCase): - def setUp(self): - pass - def test_method(self): - pass - result = unittest.TestResult() - DoesnotcallsetUp('test_method').run(result) - self.assertEqual(1, len(result.errors)) - - def test_tearDownNotCalled(self): - class DoesnotcalltearDown(TestCase): - def test_method(self): - pass - def tearDown(self): - pass - result = unittest.TestResult() - DoesnotcalltearDown('test_method').run(result) - self.assertEqual(1, len(result.errors)) - - -class TestSkipping(TestCase): - """Tests for skipping of tests functionality.""" - - run_test_with = FullStackRunTest - - def test_skip_causes_skipException(self): - self.assertThat(lambda:self.skip("Skip this test"), - Raises(MatchesException(self.skipException))) - - def test_can_use_skipTest(self): - self.assertThat(lambda:self.skipTest("Skip this test"), - Raises(MatchesException(self.skipException))) - - def test_skip_without_reason_works(self): - class Test(TestCase): - def test(self): - raise self.skipException() - case = Test("test") - result = ExtendedTestResult() - case.run(result) - self.assertEqual('addSkip', result._events[1][0]) - self.assertEqual('no reason given.', - result._events[1][2]['reason'].as_text()) - - def test_skipException_in_setup_calls_result_addSkip(self): - class TestThatRaisesInSetUp(TestCase): - def setUp(self): - TestCase.setUp(self) - self.skip("skipping this test") - def test_that_passes(self): - pass - calls = [] - result = LoggingResult(calls) - test = TestThatRaisesInSetUp("test_that_passes") - test.run(result) - case = result._events[0][1] - self.assertEqual([('startTest', case), - ('addSkip', case, "skipping this test"), ('stopTest', case)], - calls) - - def test_skipException_in_test_method_calls_result_addSkip(self): - class SkippingTest(TestCase): - def test_that_raises_skipException(self): - self.skip("skipping this test") - result = Python27TestResult() - test = SkippingTest("test_that_raises_skipException") - test.run(result) - case = result._events[0][1] - self.assertEqual([('startTest', case), - ('addSkip', case, "skipping this test"), ('stopTest', case)], - result._events) - - def test_skip__in_setup_with_old_result_object_calls_addSuccess(self): - class SkippingTest(TestCase): - def setUp(self): - TestCase.setUp(self) - raise self.skipException("skipping this test") - def test_that_raises_skipException(self): - pass - result = Python26TestResult() - test = SkippingTest("test_that_raises_skipException") - test.run(result) - self.assertEqual('addSuccess', result._events[1][0]) - - def test_skip_with_old_result_object_calls_addError(self): - class SkippingTest(TestCase): - def test_that_raises_skipException(self): - raise self.skipException("skipping this test") - result = Python26TestResult() - test = SkippingTest("test_that_raises_skipException") - test.run(result) - self.assertEqual('addSuccess', result._events[1][0]) - - def test_skip_decorator(self): - class SkippingTest(TestCase): - @skip("skipping this test") - def test_that_is_decorated_with_skip(self): - self.fail() - result = Python26TestResult() - test = SkippingTest("test_that_is_decorated_with_skip") - test.run(result) - self.assertEqual('addSuccess', result._events[1][0]) - - def test_skipIf_decorator(self): - class SkippingTest(TestCase): - @skipIf(True, "skipping this test") - def test_that_is_decorated_with_skipIf(self): - self.fail() - result = Python26TestResult() - test = SkippingTest("test_that_is_decorated_with_skipIf") - test.run(result) - self.assertEqual('addSuccess', result._events[1][0]) - - def test_skipUnless_decorator(self): - class SkippingTest(TestCase): - @skipUnless(False, "skipping this test") - def test_that_is_decorated_with_skipUnless(self): - self.fail() - result = Python26TestResult() - test = SkippingTest("test_that_is_decorated_with_skipUnless") - test.run(result) - self.assertEqual('addSuccess', result._events[1][0]) - - -class TestOnException(TestCase): - - run_test_with = FullStackRunTest - - def test_default_works(self): - events = [] - class Case(TestCase): - def method(self): - self.onException(an_exc_info) - events.append(True) - case = Case("method") - case.run() - self.assertThat(events, Equals([True])) - - def test_added_handler_works(self): - events = [] - class Case(TestCase): - def method(self): - self.addOnException(events.append) - self.onException(an_exc_info) - case = Case("method") - case.run() - self.assertThat(events, Equals([an_exc_info])) - - def test_handler_that_raises_is_not_caught(self): - events = [] - class Case(TestCase): - def method(self): - self.addOnException(events.index) - self.assertThat(lambda: self.onException(an_exc_info), - Raises(MatchesException(ValueError))) - case = Case("method") - case.run() - self.assertThat(events, Equals([])) - - -class TestPatchSupport(TestCase): - - run_test_with = FullStackRunTest - - class Case(TestCase): - def test(self): - pass - - def test_patch(self): - # TestCase.patch masks obj.attribute with the new value. - self.foo = 'original' - test = self.Case('test') - test.patch(self, 'foo', 'patched') - self.assertEqual('patched', self.foo) - - def test_patch_restored_after_run(self): - # TestCase.patch masks obj.attribute with the new value, but restores - # the original value after the test is finished. - self.foo = 'original' - test = self.Case('test') - test.patch(self, 'foo', 'patched') - test.run() - self.assertEqual('original', self.foo) - - def test_successive_patches_apply(self): - # TestCase.patch can be called multiple times per test. Each time you - # call it, it overrides the original value. - self.foo = 'original' - test = self.Case('test') - test.patch(self, 'foo', 'patched') - test.patch(self, 'foo', 'second') - self.assertEqual('second', self.foo) - - def test_successive_patches_restored_after_run(self): - # TestCase.patch restores the original value, no matter how many times - # it was called. - self.foo = 'original' - test = self.Case('test') - test.patch(self, 'foo', 'patched') - test.patch(self, 'foo', 'second') - test.run() - self.assertEqual('original', self.foo) - - def test_patch_nonexistent_attribute(self): - # TestCase.patch can be used to patch a non-existent attribute. - test = self.Case('test') - test.patch(self, 'doesntexist', 'patched') - self.assertEqual('patched', self.doesntexist) - - def test_restore_nonexistent_attribute(self): - # TestCase.patch can be used to patch a non-existent attribute, after - # the test run, the attribute is then removed from the object. - test = self.Case('test') - test.patch(self, 'doesntexist', 'patched') - test.run() - marker = object() - value = getattr(self, 'doesntexist', marker) - self.assertIs(marker, value) - - -class TestTestCaseSuper(TestCase): - - run_test_with = FullStackRunTest - - def test_setup_uses_super(self): - class OtherBaseCase(unittest.TestCase): - setup_called = False - def setUp(self): - self.setup_called = True - super(OtherBaseCase, self).setUp() - class OurCase(TestCase, OtherBaseCase): - def runTest(self): - pass - test = OurCase() - test.setUp() - test.tearDown() - self.assertTrue(test.setup_called) - - def test_teardown_uses_super(self): - class OtherBaseCase(unittest.TestCase): - teardown_called = False - def tearDown(self): - self.teardown_called = True - super(OtherBaseCase, self).tearDown() - class OurCase(TestCase, OtherBaseCase): - def runTest(self): - pass - test = OurCase() - test.setUp() - test.tearDown() - self.assertTrue(test.teardown_called) - - -class TestNullary(TestCase): - - def test_repr(self): - # The repr() of nullary is the same as the repr() of the wrapped - # function. - def foo(): - pass - wrapped = Nullary(foo) - self.assertEqual(repr(wrapped), repr(foo)) - - def test_called_with_arguments(self): - # The function is called with the arguments given to Nullary's - # constructor. - l = [] - def foo(*args, **kwargs): - l.append((args, kwargs)) - wrapped = Nullary(foo, 1, 2, a="b") - wrapped() - self.assertEqual(l, [((1, 2), {'a': 'b'})]) - - def test_returns_wrapped(self): - # Calling Nullary returns whatever the function returns. - ret = object() - wrapped = Nullary(lambda: ret) - self.assertIs(ret, wrapped()) - - def test_raises(self): - # If the function raises, so does Nullary when called. - wrapped = Nullary(lambda: 1/0) - self.assertRaises(ZeroDivisionError, wrapped) - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/test_testresult.py b/lib/testtools/testtools/tests/test_testresult.py deleted file mode 100644 index 68fcc381902..00000000000 --- a/lib/testtools/testtools/tests/test_testresult.py +++ /dev/null @@ -1,2095 +0,0 @@ -# Copyright (c) 2008-2012 testtools developers. See LICENSE for details. - -"""Test TestResults and related things.""" - -__metaclass__ = type - -import codecs -import datetime -import doctest -import os -import shutil -import sys -import tempfile -import threading -from unittest import TestSuite -import warnings - -from testtools import ( - ExtendedToOriginalDecorator, - MultiTestResult, - PlaceHolder, - Tagger, - TestCase, - TestResult, - TestResultDecorator, - TestByTestResult, - TextTestResult, - ThreadsafeForwardingResult, - testresult, - ) -from testtools.compat import ( - _b, - _get_exception_encoding, - _r, - _u, - advance_iterator, - str_is_unicode, - StringIO, - ) -from testtools.content import ( - Content, - content_from_stream, - text_content, - TracebackContent, - ) -from testtools.content_type import ContentType, UTF8_TEXT -from testtools.helpers import safe_hasattr -from testtools.matchers import ( - Contains, - DocTestMatches, - Equals, - MatchesAny, - MatchesException, - Raises, - ) -from testtools.tests.helpers import ( - an_exc_info, - FullStackRunTest, - LoggingResult, - run_with_stack_hidden, - ) -from testtools.testresult.doubles import ( - Python26TestResult, - Python27TestResult, - ExtendedTestResult, - ) -from testtools.testresult.real import ( - _details_to_str, - _merge_tags, - utc, - ) - - -def make_erroring_test(): - class Test(TestCase): - def error(self): - 1/0 - return Test("error") - - -def make_failing_test(): - class Test(TestCase): - def failed(self): - self.fail("yo!") - return Test("failed") - - -def make_mismatching_test(): - class Test(TestCase): - def mismatch(self): - self.assertEqual(1, 2) - return Test("mismatch") - - -def make_unexpectedly_successful_test(): - class Test(TestCase): - def succeeded(self): - self.expectFailure("yo!", lambda: None) - return Test("succeeded") - - -def make_test(): - class Test(TestCase): - def test(self): - pass - return Test("test") - - -def make_exception_info(exceptionFactory, *args, **kwargs): - try: - raise exceptionFactory(*args, **kwargs) - except: - return sys.exc_info() - - -class Python26Contract(object): - - def test_fresh_result_is_successful(self): - # A result is considered successful before any tests are run. - result = self.makeResult() - self.assertTrue(result.wasSuccessful()) - - def test_addError_is_failure(self): - # addError fails the test run. - result = self.makeResult() - result.startTest(self) - result.addError(self, an_exc_info) - result.stopTest(self) - self.assertFalse(result.wasSuccessful()) - - def test_addFailure_is_failure(self): - # addFailure fails the test run. - result = self.makeResult() - result.startTest(self) - result.addFailure(self, an_exc_info) - result.stopTest(self) - self.assertFalse(result.wasSuccessful()) - - def test_addSuccess_is_success(self): - # addSuccess does not fail the test run. - result = self.makeResult() - result.startTest(self) - result.addSuccess(self) - result.stopTest(self) - self.assertTrue(result.wasSuccessful()) - - def test_stop_sets_shouldStop(self): - result = self.makeResult() - result.stop() - self.assertTrue(result.shouldStop) - - -class Python27Contract(Python26Contract): - - def test_addExpectedFailure(self): - # Calling addExpectedFailure(test, exc_info) completes ok. - result = self.makeResult() - result.startTest(self) - result.addExpectedFailure(self, an_exc_info) - - def test_addExpectedFailure_is_success(self): - # addExpectedFailure does not fail the test run. - result = self.makeResult() - result.startTest(self) - result.addExpectedFailure(self, an_exc_info) - result.stopTest(self) - self.assertTrue(result.wasSuccessful()) - - def test_addSkipped(self): - # Calling addSkip(test, reason) completes ok. - result = self.makeResult() - result.startTest(self) - result.addSkip(self, _u("Skipped for some reason")) - - def test_addSkip_is_success(self): - # addSkip does not fail the test run. - result = self.makeResult() - result.startTest(self) - result.addSkip(self, _u("Skipped for some reason")) - result.stopTest(self) - self.assertTrue(result.wasSuccessful()) - - def test_addUnexpectedSuccess(self): - # Calling addUnexpectedSuccess(test) completes ok. - result = self.makeResult() - result.startTest(self) - result.addUnexpectedSuccess(self) - - def test_addUnexpectedSuccess_was_successful(self): - # addUnexpectedSuccess does not fail the test run in Python 2.7. - result = self.makeResult() - result.startTest(self) - result.addUnexpectedSuccess(self) - result.stopTest(self) - self.assertTrue(result.wasSuccessful()) - - def test_startStopTestRun(self): - # Calling startTestRun completes ok. - result = self.makeResult() - result.startTestRun() - result.stopTestRun() - - def test_failfast(self): - result = self.makeResult() - result.failfast = True - class Failing(TestCase): - def test_a(self): - self.fail('a') - def test_b(self): - self.fail('b') - TestSuite([Failing('test_a'), Failing('test_b')]).run(result) - self.assertEqual(1, result.testsRun) - - -class TagsContract(Python27Contract): - """Tests to ensure correct tagging behaviour. - - See the subunit docs for guidelines on how this is supposed to work. - """ - - def test_no_tags_by_default(self): - # Results initially have no tags. - result = self.makeResult() - self.assertEqual(frozenset(), result.current_tags) - - def test_adding_tags(self): - # Tags are added using 'tags' and thus become visible in - # 'current_tags'. - result = self.makeResult() - result.tags(set(['foo']), set()) - self.assertEqual(set(['foo']), result.current_tags) - - def test_removing_tags(self): - # Tags are removed using 'tags'. - result = self.makeResult() - result.tags(set(['foo']), set()) - result.tags(set(), set(['foo'])) - self.assertEqual(set(), result.current_tags) - - def test_startTestRun_resets_tags(self): - # startTestRun makes a new test run, and thus clears all the tags. - result = self.makeResult() - result.tags(set(['foo']), set()) - result.startTestRun() - self.assertEqual(set(), result.current_tags) - - def test_add_tags_within_test(self): - # Tags can be added after a test has run. - result = self.makeResult() - result.startTestRun() - result.tags(set(['foo']), set()) - result.startTest(self) - result.tags(set(['bar']), set()) - self.assertEqual(set(['foo', 'bar']), result.current_tags) - - def test_tags_added_in_test_are_reverted(self): - # Tags added during a test run are then reverted once that test has - # finished. - result = self.makeResult() - result.startTestRun() - result.tags(set(['foo']), set()) - result.startTest(self) - result.tags(set(['bar']), set()) - result.addSuccess(self) - result.stopTest(self) - self.assertEqual(set(['foo']), result.current_tags) - - def test_tags_removed_in_test(self): - # Tags can be removed during tests. - result = self.makeResult() - result.startTestRun() - result.tags(set(['foo']), set()) - result.startTest(self) - result.tags(set(), set(['foo'])) - self.assertEqual(set(), result.current_tags) - - def test_tags_removed_in_test_are_restored(self): - # Tags removed during tests are restored once that test has finished. - result = self.makeResult() - result.startTestRun() - result.tags(set(['foo']), set()) - result.startTest(self) - result.tags(set(), set(['foo'])) - result.addSuccess(self) - result.stopTest(self) - self.assertEqual(set(['foo']), result.current_tags) - - -class DetailsContract(TagsContract): - """Tests for the details API of TestResults.""" - - def test_addExpectedFailure_details(self): - # Calling addExpectedFailure(test, details=xxx) completes ok. - result = self.makeResult() - result.startTest(self) - result.addExpectedFailure(self, details={}) - - def test_addError_details(self): - # Calling addError(test, details=xxx) completes ok. - result = self.makeResult() - result.startTest(self) - result.addError(self, details={}) - - def test_addFailure_details(self): - # Calling addFailure(test, details=xxx) completes ok. - result = self.makeResult() - result.startTest(self) - result.addFailure(self, details={}) - - def test_addSkipped_details(self): - # Calling addSkip(test, reason) completes ok. - result = self.makeResult() - result.startTest(self) - result.addSkip(self, details={}) - - def test_addUnexpectedSuccess_details(self): - # Calling addUnexpectedSuccess(test) completes ok. - result = self.makeResult() - result.startTest(self) - result.addUnexpectedSuccess(self, details={}) - - def test_addSuccess_details(self): - # Calling addSuccess(test) completes ok. - result = self.makeResult() - result.startTest(self) - result.addSuccess(self, details={}) - - -class FallbackContract(DetailsContract): - """When we fallback we take our policy choice to map calls. - - For instance, we map unexpectedSuccess to an error code, not to success. - """ - - def test_addUnexpectedSuccess_was_successful(self): - # addUnexpectedSuccess fails test run in testtools. - result = self.makeResult() - result.startTest(self) - result.addUnexpectedSuccess(self) - result.stopTest(self) - self.assertFalse(result.wasSuccessful()) - - -class StartTestRunContract(FallbackContract): - """Defines the contract for testtools policy choices. - - That is things which are not simply extensions to unittest but choices we - have made differently. - """ - - def test_startTestRun_resets_unexpected_success(self): - result = self.makeResult() - result.startTest(self) - result.addUnexpectedSuccess(self) - result.stopTest(self) - result.startTestRun() - self.assertTrue(result.wasSuccessful()) - - def test_startTestRun_resets_failure(self): - result = self.makeResult() - result.startTest(self) - result.addFailure(self, an_exc_info) - result.stopTest(self) - result.startTestRun() - self.assertTrue(result.wasSuccessful()) - - def test_startTestRun_resets_errors(self): - result = self.makeResult() - result.startTest(self) - result.addError(self, an_exc_info) - result.stopTest(self) - result.startTestRun() - self.assertTrue(result.wasSuccessful()) - - -class TestTestResultContract(TestCase, StartTestRunContract): - - run_test_with = FullStackRunTest - - def makeResult(self): - return TestResult() - - -class TestMultiTestResultContract(TestCase, StartTestRunContract): - - run_test_with = FullStackRunTest - - def makeResult(self): - return MultiTestResult(TestResult(), TestResult()) - - -class TestTextTestResultContract(TestCase, StartTestRunContract): - - run_test_with = FullStackRunTest - - def makeResult(self): - return TextTestResult(StringIO()) - - -class TestThreadSafeForwardingResultContract(TestCase, StartTestRunContract): - - run_test_with = FullStackRunTest - - def makeResult(self): - result_semaphore = threading.Semaphore(1) - target = TestResult() - return ThreadsafeForwardingResult(target, result_semaphore) - - -class TestExtendedTestResultContract(TestCase, StartTestRunContract): - - def makeResult(self): - return ExtendedTestResult() - - -class TestPython26TestResultContract(TestCase, Python26Contract): - - def makeResult(self): - return Python26TestResult() - - -class TestAdaptedPython26TestResultContract(TestCase, FallbackContract): - - def makeResult(self): - return ExtendedToOriginalDecorator(Python26TestResult()) - - -class TestPython27TestResultContract(TestCase, Python27Contract): - - def makeResult(self): - return Python27TestResult() - - -class TestAdaptedPython27TestResultContract(TestCase, DetailsContract): - - def makeResult(self): - return ExtendedToOriginalDecorator(Python27TestResult()) - - -class TestTestResultDecoratorContract(TestCase, StartTestRunContract): - - run_test_with = FullStackRunTest - - def makeResult(self): - return TestResultDecorator(TestResult()) - - -class TestTestResult(TestCase): - """Tests for 'TestResult'.""" - - run_tests_with = FullStackRunTest - - def makeResult(self): - """Make an arbitrary result for testing.""" - return TestResult() - - def test_addSkipped(self): - # Calling addSkip on a TestResult records the test that was skipped in - # its skip_reasons dict. - result = self.makeResult() - result.addSkip(self, _u("Skipped for some reason")) - self.assertEqual({_u("Skipped for some reason"):[self]}, - result.skip_reasons) - result.addSkip(self, _u("Skipped for some reason")) - self.assertEqual({_u("Skipped for some reason"):[self, self]}, - result.skip_reasons) - result.addSkip(self, _u("Skipped for another reason")) - self.assertEqual({_u("Skipped for some reason"):[self, self], - _u("Skipped for another reason"):[self]}, - result.skip_reasons) - - def test_now_datetime_now(self): - result = self.makeResult() - olddatetime = testresult.real.datetime - def restore(): - testresult.real.datetime = olddatetime - self.addCleanup(restore) - class Module: - pass - now = datetime.datetime.now(utc) - stubdatetime = Module() - stubdatetime.datetime = Module() - stubdatetime.datetime.now = lambda tz: now - testresult.real.datetime = stubdatetime - # Calling _now() looks up the time. - self.assertEqual(now, result._now()) - then = now + datetime.timedelta(0, 1) - # Set an explicit datetime, which gets returned from then on. - result.time(then) - self.assertNotEqual(now, result._now()) - self.assertEqual(then, result._now()) - # go back to looking it up. - result.time(None) - self.assertEqual(now, result._now()) - - def test_now_datetime_time(self): - result = self.makeResult() - now = datetime.datetime.now(utc) - result.time(now) - self.assertEqual(now, result._now()) - - def test_traceback_formatting_without_stack_hidden(self): - # During the testtools test run, we show our levels of the stack, - # because we want to be able to use our test suite to debug our own - # code. - result = self.makeResult() - test = make_erroring_test() - test.run(result) - self.assertThat( - result.errors[0][1], - DocTestMatches( - 'Traceback (most recent call last):\n' - ' File "...testtools...runtest.py", line ..., in _run_user\n' - ' return fn(*args, **kwargs)\n' - ' File "...testtools...testcase.py", line ..., in _run_test_method\n' - ' return self._get_test_method()()\n' - ' File "...testtools...tests...test_testresult.py", line ..., in error\n' - ' 1/0\n' - 'ZeroDivisionError: ...\n', - doctest.ELLIPSIS | doctest.REPORT_UDIFF)) - - def test_traceback_formatting_with_stack_hidden(self): - result = self.makeResult() - test = make_erroring_test() - run_with_stack_hidden(True, test.run, result) - self.assertThat( - result.errors[0][1], - DocTestMatches( - 'Traceback (most recent call last):\n' - ' File "...testtools...tests...test_testresult.py", line ..., in error\n' - ' 1/0\n' - 'ZeroDivisionError: ...\n', - doctest.ELLIPSIS)) - - def test_traceback_formatting_with_stack_hidden_mismatch(self): - result = self.makeResult() - test = make_mismatching_test() - run_with_stack_hidden(True, test.run, result) - self.assertThat( - result.failures[0][1], - DocTestMatches( - 'Traceback (most recent call last):\n' - ' File "...testtools...tests...test_testresult.py", line ..., in mismatch\n' - ' self.assertEqual(1, 2)\n' - '...MismatchError: 1 != 2\n', - doctest.ELLIPSIS)) - - def test_exc_info_to_unicode(self): - # subunit upcalls to TestResult._exc_info_to_unicode, so we need to - # make sure that it's there. - # - # See . - test = make_erroring_test() - exc_info = make_exception_info(RuntimeError, "foo") - result = self.makeResult() - text_traceback = result._exc_info_to_unicode(exc_info, test) - self.assertEqual( - TracebackContent(exc_info, test).as_text(), text_traceback) - - -class TestMultiTestResult(TestCase): - """Tests for 'MultiTestResult'.""" - - def setUp(self): - super(TestMultiTestResult, self).setUp() - self.result1 = LoggingResult([]) - self.result2 = LoggingResult([]) - self.multiResult = MultiTestResult(self.result1, self.result2) - - def assertResultLogsEqual(self, expectedEvents): - """Assert that our test results have received the expected events.""" - self.assertEqual(expectedEvents, self.result1._events) - self.assertEqual(expectedEvents, self.result2._events) - - def test_repr(self): - self.assertEqual( - '' % ( - ExtendedToOriginalDecorator(self.result1), - ExtendedToOriginalDecorator(self.result2)), - repr(self.multiResult)) - - def test_empty(self): - # Initializing a `MultiTestResult` doesn't do anything to its - # `TestResult`s. - self.assertResultLogsEqual([]) - - def test_failfast_get(self): - # Reading reads from the first one - arbitrary choice. - self.assertEqual(False, self.multiResult.failfast) - self.result1.failfast = True - self.assertEqual(True, self.multiResult.failfast) - - def test_failfast_set(self): - # Writing writes to all. - self.multiResult.failfast = True - self.assertEqual(True, self.result1.failfast) - self.assertEqual(True, self.result2.failfast) - - def test_shouldStop(self): - self.assertFalse(self.multiResult.shouldStop) - self.result2.stop() - # NB: result1 is not stopped: MultiTestResult has to combine the - # values. - self.assertTrue(self.multiResult.shouldStop) - - def test_startTest(self): - # Calling `startTest` on a `MultiTestResult` calls `startTest` on all - # its `TestResult`s. - self.multiResult.startTest(self) - self.assertResultLogsEqual([('startTest', self)]) - - def test_stop(self): - self.assertFalse(self.multiResult.shouldStop) - self.multiResult.stop() - self.assertResultLogsEqual(['stop']) - - def test_stopTest(self): - # Calling `stopTest` on a `MultiTestResult` calls `stopTest` on all - # its `TestResult`s. - self.multiResult.stopTest(self) - self.assertResultLogsEqual([('stopTest', self)]) - - def test_addSkipped(self): - # Calling `addSkip` on a `MultiTestResult` calls addSkip on its - # results. - reason = _u("Skipped for some reason") - self.multiResult.addSkip(self, reason) - self.assertResultLogsEqual([('addSkip', self, reason)]) - - def test_addSuccess(self): - # Calling `addSuccess` on a `MultiTestResult` calls `addSuccess` on - # all its `TestResult`s. - self.multiResult.addSuccess(self) - self.assertResultLogsEqual([('addSuccess', self)]) - - def test_done(self): - # Calling `done` on a `MultiTestResult` calls `done` on all its - # `TestResult`s. - self.multiResult.done() - self.assertResultLogsEqual([('done')]) - - def test_addFailure(self): - # Calling `addFailure` on a `MultiTestResult` calls `addFailure` on - # all its `TestResult`s. - exc_info = make_exception_info(AssertionError, 'failure') - self.multiResult.addFailure(self, exc_info) - self.assertResultLogsEqual([('addFailure', self, exc_info)]) - - def test_addError(self): - # Calling `addError` on a `MultiTestResult` calls `addError` on all - # its `TestResult`s. - exc_info = make_exception_info(RuntimeError, 'error') - self.multiResult.addError(self, exc_info) - self.assertResultLogsEqual([('addError', self, exc_info)]) - - def test_startTestRun(self): - # Calling `startTestRun` on a `MultiTestResult` forwards to all its - # `TestResult`s. - self.multiResult.startTestRun() - self.assertResultLogsEqual([('startTestRun')]) - - def test_stopTestRun(self): - # Calling `stopTestRun` on a `MultiTestResult` forwards to all its - # `TestResult`s. - self.multiResult.stopTestRun() - self.assertResultLogsEqual([('stopTestRun')]) - - def test_stopTestRun_returns_results(self): - # `MultiTestResult.stopTestRun` returns a tuple of all of the return - # values the `stopTestRun`s that it forwards to. - class Result(LoggingResult): - def stopTestRun(self): - super(Result, self).stopTestRun() - return 'foo' - multi_result = MultiTestResult(Result([]), Result([])) - result = multi_result.stopTestRun() - self.assertEqual(('foo', 'foo'), result) - - def test_tags(self): - # Calling `tags` on a `MultiTestResult` calls `tags` on all its - # `TestResult`s. - added_tags = set(['foo', 'bar']) - removed_tags = set(['eggs']) - self.multiResult.tags(added_tags, removed_tags) - self.assertResultLogsEqual([('tags', added_tags, removed_tags)]) - - def test_time(self): - # the time call is dispatched, not eaten by the base class - self.multiResult.time('foo') - self.assertResultLogsEqual([('time', 'foo')]) - - -class TestTextTestResult(TestCase): - """Tests for 'TextTestResult'.""" - - def setUp(self): - super(TestTextTestResult, self).setUp() - self.result = TextTestResult(StringIO()) - - def getvalue(self): - return self.result.stream.getvalue() - - def test__init_sets_stream(self): - result = TextTestResult("fp") - self.assertEqual("fp", result.stream) - - def reset_output(self): - self.result.stream = StringIO() - - def test_startTestRun(self): - self.result.startTestRun() - self.assertEqual("Tests running...\n", self.getvalue()) - - def test_stopTestRun_count_many(self): - test = make_test() - self.result.startTestRun() - self.result.startTest(test) - self.result.stopTest(test) - self.result.startTest(test) - self.result.stopTest(test) - self.result.stream = StringIO() - self.result.stopTestRun() - self.assertThat(self.getvalue(), - DocTestMatches("\nRan 2 tests in ...s\n...", doctest.ELLIPSIS)) - - def test_stopTestRun_count_single(self): - test = make_test() - self.result.startTestRun() - self.result.startTest(test) - self.result.stopTest(test) - self.reset_output() - self.result.stopTestRun() - self.assertThat(self.getvalue(), - DocTestMatches("\nRan 1 test in ...s\nOK\n", doctest.ELLIPSIS)) - - def test_stopTestRun_count_zero(self): - self.result.startTestRun() - self.reset_output() - self.result.stopTestRun() - self.assertThat(self.getvalue(), - DocTestMatches("\nRan 0 tests in ...s\nOK\n", doctest.ELLIPSIS)) - - def test_stopTestRun_current_time(self): - test = make_test() - now = datetime.datetime.now(utc) - self.result.time(now) - self.result.startTestRun() - self.result.startTest(test) - now = now + datetime.timedelta(0, 0, 0, 1) - self.result.time(now) - self.result.stopTest(test) - self.reset_output() - self.result.stopTestRun() - self.assertThat(self.getvalue(), - DocTestMatches("... in 0.001s\n...", doctest.ELLIPSIS)) - - def test_stopTestRun_successful(self): - self.result.startTestRun() - self.result.stopTestRun() - self.assertThat(self.getvalue(), - DocTestMatches("...\nOK\n", doctest.ELLIPSIS)) - - def test_stopTestRun_not_successful_failure(self): - test = make_failing_test() - self.result.startTestRun() - test.run(self.result) - self.result.stopTestRun() - self.assertThat(self.getvalue(), - DocTestMatches("...\nFAILED (failures=1)\n", doctest.ELLIPSIS)) - - def test_stopTestRun_not_successful_error(self): - test = make_erroring_test() - self.result.startTestRun() - test.run(self.result) - self.result.stopTestRun() - self.assertThat(self.getvalue(), - DocTestMatches("...\nFAILED (failures=1)\n", doctest.ELLIPSIS)) - - def test_stopTestRun_not_successful_unexpected_success(self): - test = make_unexpectedly_successful_test() - self.result.startTestRun() - test.run(self.result) - self.result.stopTestRun() - self.assertThat(self.getvalue(), - DocTestMatches("...\nFAILED (failures=1)\n", doctest.ELLIPSIS)) - - def test_stopTestRun_shows_details(self): - def run_tests(): - self.result.startTestRun() - make_erroring_test().run(self.result) - make_unexpectedly_successful_test().run(self.result) - make_failing_test().run(self.result) - self.reset_output() - self.result.stopTestRun() - run_with_stack_hidden(True, run_tests) - self.assertThat(self.getvalue(), - DocTestMatches("""...====================================================================== -ERROR: testtools.tests.test_testresult.Test.error ----------------------------------------------------------------------- -Traceback (most recent call last): - File "...testtools...tests...test_testresult.py", line ..., in error - 1/0 -ZeroDivisionError:... divi... by zero... -====================================================================== -FAIL: testtools.tests.test_testresult.Test.failed ----------------------------------------------------------------------- -Traceback (most recent call last): - File "...testtools...tests...test_testresult.py", line ..., in failed - self.fail("yo!") -AssertionError: yo! -====================================================================== -UNEXPECTED SUCCESS: testtools.tests.test_testresult.Test.succeeded ----------------------------------------------------------------------- -...""", doctest.ELLIPSIS | doctest.REPORT_NDIFF)) - - -class TestThreadSafeForwardingResult(TestCase): - """Tests for `TestThreadSafeForwardingResult`.""" - - def make_results(self, n): - events = [] - target = LoggingResult(events) - semaphore = threading.Semaphore(1) - return [ - ThreadsafeForwardingResult(target, semaphore) - for i in range(n)], events - - def test_nonforwarding_methods(self): - # startTest and stopTest are not forwarded because they need to be - # batched. - [result], events = self.make_results(1) - result.startTest(self) - result.stopTest(self) - self.assertEqual([], events) - - def test_tags_not_forwarded(self): - # Tags need to be batched for each test, so they aren't forwarded - # until a test runs. - [result], events = self.make_results(1) - result.tags(set(['foo']), set(['bar'])) - self.assertEqual([], events) - - def test_global_tags_simple(self): - # Tags specified outside of a test result are global. When a test's - # results are finally forwarded, we send through these global tags - # *as* test specific tags, because as a multiplexer there should be no - # way for a global tag on an input stream to affect tests from other - # streams - we can just always issue test local tags. - [result], events = self.make_results(1) - result.tags(set(['foo']), set()) - result.time(1) - result.startTest(self) - result.time(2) - result.addSuccess(self) - self.assertEqual( - [('time', 1), - ('startTest', self), - ('time', 2), - ('tags', set(['foo']), set()), - ('addSuccess', self), - ('stopTest', self), - ], events) - - def test_global_tags_complex(self): - # Multiple calls to tags() in a global context are buffered until the - # next test completes and are issued as part of of the test context, - # because they cannot be issued until the output result is locked. - # The sample data shows them being merged together, this is, strictly - # speaking incidental - they could be issued separately (in-order) and - # still be legitimate. - [result], events = self.make_results(1) - result.tags(set(['foo', 'bar']), set(['baz', 'qux'])) - result.tags(set(['cat', 'qux']), set(['bar', 'dog'])) - result.time(1) - result.startTest(self) - result.time(2) - result.addSuccess(self) - self.assertEqual( - [('time', 1), - ('startTest', self), - ('time', 2), - ('tags', set(['cat', 'foo', 'qux']), set(['dog', 'bar', 'baz'])), - ('addSuccess', self), - ('stopTest', self), - ], events) - - def test_local_tags(self): - # Any tags set within a test context are forwarded in that test - # context when the result is finally forwarded. This means that the - # tags for the test are part of the atomic message communicating - # everything about that test. - [result], events = self.make_results(1) - result.time(1) - result.startTest(self) - result.tags(set(['foo']), set([])) - result.tags(set(), set(['bar'])) - result.time(2) - result.addSuccess(self) - self.assertEqual( - [('time', 1), - ('startTest', self), - ('time', 2), - ('tags', set(['foo']), set(['bar'])), - ('addSuccess', self), - ('stopTest', self), - ], events) - - def test_local_tags_dont_leak(self): - # A tag set during a test is local to that test and is not set during - # the tests that follow. - [result], events = self.make_results(1) - a, b = PlaceHolder('a'), PlaceHolder('b') - result.time(1) - result.startTest(a) - result.tags(set(['foo']), set([])) - result.time(2) - result.addSuccess(a) - result.stopTest(a) - result.time(3) - result.startTest(b) - result.time(4) - result.addSuccess(b) - result.stopTest(b) - self.assertEqual( - [('time', 1), - ('startTest', a), - ('time', 2), - ('tags', set(['foo']), set()), - ('addSuccess', a), - ('stopTest', a), - ('time', 3), - ('startTest', b), - ('time', 4), - ('addSuccess', b), - ('stopTest', b), - ], events) - - def test_startTestRun(self): - # Calls to startTestRun are not batched, because we are only - # interested in sending tests atomically, not the whole run. - [result1, result2], events = self.make_results(2) - result1.startTestRun() - result2.startTestRun() - self.assertEqual(["startTestRun", "startTestRun"], events) - - def test_stopTestRun(self): - # Calls to stopTestRun are not batched, because we are only - # interested in sending tests atomically, not the whole run. - [result1, result2], events = self.make_results(2) - result1.stopTestRun() - result2.stopTestRun() - self.assertEqual(["stopTestRun", "stopTestRun"], events) - - def test_forward_addError(self): - # Once we receive an addError event, we forward all of the events for - # that test, as we now know that test is complete. - [result], events = self.make_results(1) - exc_info = make_exception_info(RuntimeError, 'error') - start_time = datetime.datetime.utcfromtimestamp(1.489) - end_time = datetime.datetime.utcfromtimestamp(51.476) - result.time(start_time) - result.startTest(self) - result.time(end_time) - result.addError(self, exc_info) - self.assertEqual([ - ('time', start_time), - ('startTest', self), - ('time', end_time), - ('addError', self, exc_info), - ('stopTest', self), - ], events) - - def test_forward_addFailure(self): - # Once we receive an addFailure event, we forward all of the events - # for that test, as we now know that test is complete. - [result], events = self.make_results(1) - exc_info = make_exception_info(AssertionError, 'failure') - start_time = datetime.datetime.utcfromtimestamp(2.489) - end_time = datetime.datetime.utcfromtimestamp(3.476) - result.time(start_time) - result.startTest(self) - result.time(end_time) - result.addFailure(self, exc_info) - self.assertEqual([ - ('time', start_time), - ('startTest', self), - ('time', end_time), - ('addFailure', self, exc_info), - ('stopTest', self), - ], events) - - def test_forward_addSkip(self): - # Once we receive an addSkip event, we forward all of the events for - # that test, as we now know that test is complete. - [result], events = self.make_results(1) - reason = _u("Skipped for some reason") - start_time = datetime.datetime.utcfromtimestamp(4.489) - end_time = datetime.datetime.utcfromtimestamp(5.476) - result.time(start_time) - result.startTest(self) - result.time(end_time) - result.addSkip(self, reason) - self.assertEqual([ - ('time', start_time), - ('startTest', self), - ('time', end_time), - ('addSkip', self, reason), - ('stopTest', self), - ], events) - - def test_forward_addSuccess(self): - # Once we receive an addSuccess event, we forward all of the events - # for that test, as we now know that test is complete. - [result], events = self.make_results(1) - start_time = datetime.datetime.utcfromtimestamp(6.489) - end_time = datetime.datetime.utcfromtimestamp(7.476) - result.time(start_time) - result.startTest(self) - result.time(end_time) - result.addSuccess(self) - self.assertEqual([ - ('time', start_time), - ('startTest', self), - ('time', end_time), - ('addSuccess', self), - ('stopTest', self), - ], events) - - def test_only_one_test_at_a_time(self): - # Even if there are multiple ThreadsafeForwardingResults forwarding to - # the same target result, the target result only receives the complete - # events for one test at a time. - [result1, result2], events = self.make_results(2) - test1, test2 = self, make_test() - start_time1 = datetime.datetime.utcfromtimestamp(1.489) - end_time1 = datetime.datetime.utcfromtimestamp(2.476) - start_time2 = datetime.datetime.utcfromtimestamp(3.489) - end_time2 = datetime.datetime.utcfromtimestamp(4.489) - result1.time(start_time1) - result2.time(start_time2) - result1.startTest(test1) - result2.startTest(test2) - result1.time(end_time1) - result2.time(end_time2) - result2.addSuccess(test2) - result1.addSuccess(test1) - self.assertEqual([ - # test2 finishes first, and so is flushed first. - ('time', start_time2), - ('startTest', test2), - ('time', end_time2), - ('addSuccess', test2), - ('stopTest', test2), - # test1 finishes next, and thus follows. - ('time', start_time1), - ('startTest', test1), - ('time', end_time1), - ('addSuccess', test1), - ('stopTest', test1), - ], events) - - -class TestMergeTags(TestCase): - - def test_merge_unseen_gone_tag(self): - # If an incoming "gone" tag isn't currently tagged one way or the - # other, add it to the "gone" tags. - current_tags = set(['present']), set(['missing']) - changing_tags = set(), set(['going']) - expected = set(['present']), set(['missing', 'going']) - self.assertEqual( - expected, _merge_tags(current_tags, changing_tags)) - - def test_merge_incoming_gone_tag_with_current_new_tag(self): - # If one of the incoming "gone" tags is one of the existing "new" - # tags, then it overrides the "new" tag, leaving it marked as "gone". - current_tags = set(['present', 'going']), set(['missing']) - changing_tags = set(), set(['going']) - expected = set(['present']), set(['missing', 'going']) - self.assertEqual( - expected, _merge_tags(current_tags, changing_tags)) - - def test_merge_unseen_new_tag(self): - current_tags = set(['present']), set(['missing']) - changing_tags = set(['coming']), set() - expected = set(['coming', 'present']), set(['missing']) - self.assertEqual( - expected, _merge_tags(current_tags, changing_tags)) - - def test_merge_incoming_new_tag_with_current_gone_tag(self): - # If one of the incoming "new" tags is currently marked as "gone", - # then it overrides the "gone" tag, leaving it marked as "new". - current_tags = set(['present']), set(['coming', 'missing']) - changing_tags = set(['coming']), set() - expected = set(['coming', 'present']), set(['missing']) - self.assertEqual( - expected, _merge_tags(current_tags, changing_tags)) - - -class TestExtendedToOriginalResultDecoratorBase(TestCase): - - def make_26_result(self): - self.result = Python26TestResult() - self.make_converter() - - def make_27_result(self): - self.result = Python27TestResult() - self.make_converter() - - def make_converter(self): - self.converter = ExtendedToOriginalDecorator(self.result) - - def make_extended_result(self): - self.result = ExtendedTestResult() - self.make_converter() - - def check_outcome_details(self, outcome): - """Call an outcome with a details dict to be passed through.""" - # This dict is /not/ convertible - thats deliberate, as it should - # not hit the conversion code path. - details = {'foo': 'bar'} - getattr(self.converter, outcome)(self, details=details) - self.assertEqual([(outcome, self, details)], self.result._events) - - def get_details_and_string(self): - """Get a details dict and expected string.""" - text1 = lambda: [_b("1\n2\n")] - text2 = lambda: [_b("3\n4\n")] - bin1 = lambda: [_b("5\n")] - details = {'text 1': Content(ContentType('text', 'plain'), text1), - 'text 2': Content(ContentType('text', 'strange'), text2), - 'bin 1': Content(ContentType('application', 'binary'), bin1)} - return (details, - ("Binary content:\n" - " bin 1 (application/binary)\n" - "\n" - "text 1: {{{\n" - "1\n" - "2\n" - "}}}\n" - "\n" - "text 2: {{{\n" - "3\n" - "4\n" - "}}}\n")) - - def check_outcome_details_to_exec_info(self, outcome, expected=None): - """Call an outcome with a details dict to be made into exc_info.""" - # The conversion is a done using RemoteError and the string contents - # of the text types in the details dict. - if not expected: - expected = outcome - details, err_str = self.get_details_and_string() - getattr(self.converter, outcome)(self, details=details) - err = self.converter._details_to_exc_info(details) - self.assertEqual([(expected, self, err)], self.result._events) - - def check_outcome_details_to_nothing(self, outcome, expected=None): - """Call an outcome with a details dict to be swallowed.""" - if not expected: - expected = outcome - details = {'foo': 'bar'} - getattr(self.converter, outcome)(self, details=details) - self.assertEqual([(expected, self)], self.result._events) - - def check_outcome_details_to_string(self, outcome): - """Call an outcome with a details dict to be stringified.""" - details, err_str = self.get_details_and_string() - getattr(self.converter, outcome)(self, details=details) - self.assertEqual([(outcome, self, err_str)], self.result._events) - - def check_outcome_details_to_arg(self, outcome, arg, extra_detail=None): - """Call an outcome with a details dict to have an arg extracted.""" - details, _ = self.get_details_and_string() - if extra_detail: - details.update(extra_detail) - getattr(self.converter, outcome)(self, details=details) - self.assertEqual([(outcome, self, arg)], self.result._events) - - def check_outcome_exc_info(self, outcome, expected=None): - """Check that calling a legacy outcome still works.""" - # calling some outcome with the legacy exc_info style api (no keyword - # parameters) gets passed through. - if not expected: - expected = outcome - err = sys.exc_info() - getattr(self.converter, outcome)(self, err) - self.assertEqual([(expected, self, err)], self.result._events) - - def check_outcome_exc_info_to_nothing(self, outcome, expected=None): - """Check that calling a legacy outcome on a fallback works.""" - # calling some outcome with the legacy exc_info style api (no keyword - # parameters) gets passed through. - if not expected: - expected = outcome - err = sys.exc_info() - getattr(self.converter, outcome)(self, err) - self.assertEqual([(expected, self)], self.result._events) - - def check_outcome_nothing(self, outcome, expected=None): - """Check that calling a legacy outcome still works.""" - if not expected: - expected = outcome - getattr(self.converter, outcome)(self) - self.assertEqual([(expected, self)], self.result._events) - - def check_outcome_string_nothing(self, outcome, expected): - """Check that calling outcome with a string calls expected.""" - getattr(self.converter, outcome)(self, "foo") - self.assertEqual([(expected, self)], self.result._events) - - def check_outcome_string(self, outcome): - """Check that calling outcome with a string works.""" - getattr(self.converter, outcome)(self, "foo") - self.assertEqual([(outcome, self, "foo")], self.result._events) - - -class TestExtendedToOriginalResultDecorator( - TestExtendedToOriginalResultDecoratorBase): - - def test_failfast_py26(self): - self.make_26_result() - self.assertEqual(False, self.converter.failfast) - self.converter.failfast = True - self.assertFalse(safe_hasattr(self.converter.decorated, 'failfast')) - - def test_failfast_py27(self): - self.make_27_result() - self.assertEqual(False, self.converter.failfast) - # setting it should write it to the backing result - self.converter.failfast = True - self.assertEqual(True, self.converter.decorated.failfast) - - def test_progress_py26(self): - self.make_26_result() - self.converter.progress(1, 2) - - def test_progress_py27(self): - self.make_27_result() - self.converter.progress(1, 2) - - def test_progress_pyextended(self): - self.make_extended_result() - self.converter.progress(1, 2) - self.assertEqual([('progress', 1, 2)], self.result._events) - - def test_shouldStop(self): - self.make_26_result() - self.assertEqual(False, self.converter.shouldStop) - self.converter.decorated.stop() - self.assertEqual(True, self.converter.shouldStop) - - def test_startTest_py26(self): - self.make_26_result() - self.converter.startTest(self) - self.assertEqual([('startTest', self)], self.result._events) - - def test_startTest_py27(self): - self.make_27_result() - self.converter.startTest(self) - self.assertEqual([('startTest', self)], self.result._events) - - def test_startTest_pyextended(self): - self.make_extended_result() - self.converter.startTest(self) - self.assertEqual([('startTest', self)], self.result._events) - - def test_startTestRun_py26(self): - self.make_26_result() - self.converter.startTestRun() - self.assertEqual([], self.result._events) - - def test_startTestRun_py27(self): - self.make_27_result() - self.converter.startTestRun() - self.assertEqual([('startTestRun',)], self.result._events) - - def test_startTestRun_pyextended(self): - self.make_extended_result() - self.converter.startTestRun() - self.assertEqual([('startTestRun',)], self.result._events) - - def test_stopTest_py26(self): - self.make_26_result() - self.converter.stopTest(self) - self.assertEqual([('stopTest', self)], self.result._events) - - def test_stopTest_py27(self): - self.make_27_result() - self.converter.stopTest(self) - self.assertEqual([('stopTest', self)], self.result._events) - - def test_stopTest_pyextended(self): - self.make_extended_result() - self.converter.stopTest(self) - self.assertEqual([('stopTest', self)], self.result._events) - - def test_stopTestRun_py26(self): - self.make_26_result() - self.converter.stopTestRun() - self.assertEqual([], self.result._events) - - def test_stopTestRun_py27(self): - self.make_27_result() - self.converter.stopTestRun() - self.assertEqual([('stopTestRun',)], self.result._events) - - def test_stopTestRun_pyextended(self): - self.make_extended_result() - self.converter.stopTestRun() - self.assertEqual([('stopTestRun',)], self.result._events) - - def test_tags_py26(self): - self.make_26_result() - self.converter.tags(set([1]), set([2])) - - def test_tags_py27(self): - self.make_27_result() - self.converter.tags(set([1]), set([2])) - - def test_tags_pyextended(self): - self.make_extended_result() - self.converter.tags(set([1]), set([2])) - self.assertEqual([('tags', set([1]), set([2]))], self.result._events) - - def test_time_py26(self): - self.make_26_result() - self.converter.time(1) - - def test_time_py27(self): - self.make_27_result() - self.converter.time(1) - - def test_time_pyextended(self): - self.make_extended_result() - self.converter.time(1) - self.assertEqual([('time', 1)], self.result._events) - - -class TestExtendedToOriginalAddError(TestExtendedToOriginalResultDecoratorBase): - - outcome = 'addError' - - def test_outcome_Original_py26(self): - self.make_26_result() - self.check_outcome_exc_info(self.outcome) - - def test_outcome_Original_py27(self): - self.make_27_result() - self.check_outcome_exc_info(self.outcome) - - def test_outcome_Original_pyextended(self): - self.make_extended_result() - self.check_outcome_exc_info(self.outcome) - - def test_outcome_Extended_py26(self): - self.make_26_result() - self.check_outcome_details_to_exec_info(self.outcome) - - def test_outcome_Extended_py27(self): - self.make_27_result() - self.check_outcome_details_to_exec_info(self.outcome) - - def test_outcome_Extended_pyextended(self): - self.make_extended_result() - self.check_outcome_details(self.outcome) - - def test_outcome__no_details(self): - self.make_extended_result() - self.assertThat( - lambda: getattr(self.converter, self.outcome)(self), - Raises(MatchesException(ValueError))) - - -class TestExtendedToOriginalAddFailure( - TestExtendedToOriginalAddError): - - outcome = 'addFailure' - - -class TestExtendedToOriginalAddExpectedFailure( - TestExtendedToOriginalAddError): - - outcome = 'addExpectedFailure' - - def test_outcome_Original_py26(self): - self.make_26_result() - self.check_outcome_exc_info_to_nothing(self.outcome, 'addSuccess') - - def test_outcome_Extended_py26(self): - self.make_26_result() - self.check_outcome_details_to_nothing(self.outcome, 'addSuccess') - - - -class TestExtendedToOriginalAddSkip( - TestExtendedToOriginalResultDecoratorBase): - - outcome = 'addSkip' - - def test_outcome_Original_py26(self): - self.make_26_result() - self.check_outcome_string_nothing(self.outcome, 'addSuccess') - - def test_outcome_Original_py27(self): - self.make_27_result() - self.check_outcome_string(self.outcome) - - def test_outcome_Original_pyextended(self): - self.make_extended_result() - self.check_outcome_string(self.outcome) - - def test_outcome_Extended_py26(self): - self.make_26_result() - self.check_outcome_string_nothing(self.outcome, 'addSuccess') - - def test_outcome_Extended_py27_no_reason(self): - self.make_27_result() - self.check_outcome_details_to_string(self.outcome) - - def test_outcome_Extended_py27_reason(self): - self.make_27_result() - self.check_outcome_details_to_arg(self.outcome, 'foo', - {'reason': Content(UTF8_TEXT, lambda:[_b('foo')])}) - - def test_outcome_Extended_pyextended(self): - self.make_extended_result() - self.check_outcome_details(self.outcome) - - def test_outcome__no_details(self): - self.make_extended_result() - self.assertThat( - lambda: getattr(self.converter, self.outcome)(self), - Raises(MatchesException(ValueError))) - - -class TestExtendedToOriginalAddSuccess( - TestExtendedToOriginalResultDecoratorBase): - - outcome = 'addSuccess' - expected = 'addSuccess' - - def test_outcome_Original_py26(self): - self.make_26_result() - self.check_outcome_nothing(self.outcome, self.expected) - - def test_outcome_Original_py27(self): - self.make_27_result() - self.check_outcome_nothing(self.outcome) - - def test_outcome_Original_pyextended(self): - self.make_extended_result() - self.check_outcome_nothing(self.outcome) - - def test_outcome_Extended_py26(self): - self.make_26_result() - self.check_outcome_details_to_nothing(self.outcome, self.expected) - - def test_outcome_Extended_py27(self): - self.make_27_result() - self.check_outcome_details_to_nothing(self.outcome) - - def test_outcome_Extended_pyextended(self): - self.make_extended_result() - self.check_outcome_details(self.outcome) - - -class TestExtendedToOriginalAddUnexpectedSuccess( - TestExtendedToOriginalResultDecoratorBase): - - outcome = 'addUnexpectedSuccess' - expected = 'addFailure' - - def test_outcome_Original_py26(self): - self.make_26_result() - getattr(self.converter, self.outcome)(self) - [event] = self.result._events - self.assertEqual((self.expected, self), event[:2]) - - def test_outcome_Original_py27(self): - self.make_27_result() - self.check_outcome_nothing(self.outcome) - - def test_outcome_Original_pyextended(self): - self.make_extended_result() - self.check_outcome_nothing(self.outcome) - - def test_outcome_Extended_py26(self): - self.make_26_result() - getattr(self.converter, self.outcome)(self) - [event] = self.result._events - self.assertEqual((self.expected, self), event[:2]) - - def test_outcome_Extended_py27(self): - self.make_27_result() - self.check_outcome_details_to_nothing(self.outcome) - - def test_outcome_Extended_pyextended(self): - self.make_extended_result() - self.check_outcome_details(self.outcome) - - -class TestExtendedToOriginalResultOtherAttributes( - TestExtendedToOriginalResultDecoratorBase): - - def test_other_attribute(self): - class OtherExtendedResult: - def foo(self): - return 2 - bar = 1 - self.result = OtherExtendedResult() - self.make_converter() - self.assertEqual(1, self.converter.bar) - self.assertEqual(2, self.converter.foo()) - - -class TestNonAsciiResults(TestCase): - """Test all kinds of tracebacks are cleanly interpreted as unicode - - Currently only uses weak "contains" assertions, would be good to be much - stricter about the expected output. This would add a few failures for the - current release of IronPython for instance, which gets some traceback - lines muddled. - """ - - _sample_texts = ( - _u("pa\u026a\u03b8\u0259n"), # Unicode encodings only - _u("\u5357\u7121"), # In ISO 2022 encodings - _u("\xa7\xa7\xa7"), # In ISO 8859 encodings - ) - - _is_pypy = "__pypy__" in sys.builtin_module_names - # Everything but Jython shows syntax errors on the current character - _error_on_character = os.name != "java" and not _is_pypy - - def _run(self, stream, test): - """Run the test, the same as in testtools.run but not to stdout""" - result = TextTestResult(stream) - result.startTestRun() - try: - return test.run(result) - finally: - result.stopTestRun() - - def _write_module(self, name, encoding, contents): - """Create Python module on disk with contents in given encoding""" - try: - # Need to pre-check that the coding is valid or codecs.open drops - # the file without closing it which breaks non-refcounted pythons - codecs.lookup(encoding) - except LookupError: - self.skip("Encoding unsupported by implementation: %r" % encoding) - f = codecs.open(os.path.join(self.dir, name + ".py"), "w", encoding) - try: - f.write(contents) - finally: - f.close() - - def _test_external_case(self, testline, coding="ascii", modulelevel="", - suffix=""): - """Create and run a test case in a separate module""" - self._setup_external_case(testline, coding, modulelevel, suffix) - return self._run_external_case() - - def _setup_external_case(self, testline, coding="ascii", modulelevel="", - suffix=""): - """Create a test case in a separate module""" - _, prefix, self.modname = self.id().rsplit(".", 2) - self.dir = tempfile.mkdtemp(prefix=prefix, suffix=suffix) - self.addCleanup(shutil.rmtree, self.dir) - self._write_module(self.modname, coding, - # Older Python 2 versions don't see a coding declaration in a - # docstring so it has to be in a comment, but then we can't - # workaround bug: - "# coding: %s\n" - "import testtools\n" - "%s\n" - "class Test(testtools.TestCase):\n" - " def runTest(self):\n" - " %s\n" % (coding, modulelevel, testline)) - - def _run_external_case(self): - """Run the prepared test case in a separate module""" - sys.path.insert(0, self.dir) - self.addCleanup(sys.path.remove, self.dir) - module = __import__(self.modname) - self.addCleanup(sys.modules.pop, self.modname) - stream = StringIO() - self._run(stream, module.Test()) - return stream.getvalue() - - def _silence_deprecation_warnings(self): - """Shut up DeprecationWarning for this test only""" - warnings.simplefilter("ignore", DeprecationWarning) - self.addCleanup(warnings.filters.remove, warnings.filters[0]) - - def _get_sample_text(self, encoding="unicode_internal"): - if encoding is None and str_is_unicode: - encoding = "unicode_internal" - for u in self._sample_texts: - try: - b = u.encode(encoding) - if u == b.decode(encoding): - if str_is_unicode: - return u, u - return u, b - except (LookupError, UnicodeError): - pass - self.skip("Could not find a sample text for encoding: %r" % encoding) - - def _as_output(self, text): - return text - - def test_non_ascii_failure_string(self): - """Assertion contents can be non-ascii and should get decoded""" - text, raw = self._get_sample_text(_get_exception_encoding()) - textoutput = self._test_external_case("self.fail(%s)" % _r(raw)) - self.assertIn(self._as_output(text), textoutput) - - def test_non_ascii_failure_string_via_exec(self): - """Assertion via exec can be non-ascii and still gets decoded""" - text, raw = self._get_sample_text(_get_exception_encoding()) - textoutput = self._test_external_case( - testline='exec ("self.fail(%s)")' % _r(raw)) - self.assertIn(self._as_output(text), textoutput) - - def test_control_characters_in_failure_string(self): - """Control characters in assertions should be escaped""" - textoutput = self._test_external_case("self.fail('\\a\\a\\a')") - self.expectFailure("Defense against the beeping horror unimplemented", - self.assertNotIn, self._as_output("\a\a\a"), textoutput) - self.assertIn(self._as_output(_u("\uFFFD\uFFFD\uFFFD")), textoutput) - - def _local_os_error_matcher(self): - if sys.version_info > (3, 3): - return MatchesAny(Contains("FileExistsError: "), - Contains("PermissionError: ")) - elif os.name != "nt" or sys.version_info < (2, 5): - return Contains(self._as_output("OSError: ")) - else: - return Contains(self._as_output("WindowsError: ")) - - def test_os_error(self): - """Locale error messages from the OS shouldn't break anything""" - textoutput = self._test_external_case( - modulelevel="import os", - testline="os.mkdir('/')") - self.assertThat(textoutput, self._local_os_error_matcher()) - - def test_assertion_text_shift_jis(self): - """A terminal raw backslash in an encoded string is weird but fine""" - example_text = _u("\u5341") - textoutput = self._test_external_case( - coding="shift_jis", - testline="self.fail('%s')" % example_text) - if str_is_unicode: - output_text = example_text - else: - output_text = example_text.encode("shift_jis").decode( - _get_exception_encoding(), "replace") - self.assertIn(self._as_output("AssertionError: %s" % output_text), - textoutput) - - def test_file_comment_iso2022_jp(self): - """Control character escapes must be preserved if valid encoding""" - example_text, _ = self._get_sample_text("iso2022_jp") - textoutput = self._test_external_case( - coding="iso2022_jp", - testline="self.fail('Simple') # %s" % example_text) - self.assertIn(self._as_output(example_text), textoutput) - - def test_unicode_exception(self): - """Exceptions that can be formated losslessly as unicode should be""" - example_text, _ = self._get_sample_text() - exception_class = ( - "class FancyError(Exception):\n" - # A __unicode__ method does nothing on py3k but the default works - " def __unicode__(self):\n" - " return self.args[0]\n") - textoutput = self._test_external_case( - modulelevel=exception_class, - testline="raise FancyError(%s)" % _r(example_text)) - self.assertIn(self._as_output(example_text), textoutput) - - def test_unprintable_exception(self): - """A totally useless exception instance still prints something""" - exception_class = ( - "class UnprintableError(Exception):\n" - " def __str__(self):\n" - " raise RuntimeError\n" - " def __unicode__(self):\n" - " raise RuntimeError\n" - " def __repr__(self):\n" - " raise RuntimeError\n") - textoutput = self._test_external_case( - modulelevel=exception_class, - testline="raise UnprintableError") - self.assertIn(self._as_output( - "UnprintableError: \n"), - textoutput) - - def test_string_exception(self): - """Raise a string rather than an exception instance if supported""" - if sys.version_info > (2, 6): - self.skip("No string exceptions in Python 2.6 or later") - elif sys.version_info > (2, 5): - self._silence_deprecation_warnings() - textoutput = self._test_external_case(testline="raise 'plain str'") - self.assertIn(self._as_output("\nplain str\n"), textoutput) - - def test_non_ascii_dirname(self): - """Script paths in the traceback can be non-ascii""" - text, raw = self._get_sample_text(sys.getfilesystemencoding()) - textoutput = self._test_external_case( - # Avoid bug in Python 3 by giving a unicode source encoding rather - # than just ascii which raises a SyntaxError with no other details - coding="utf-8", - testline="self.fail('Simple')", - suffix=raw) - self.assertIn(self._as_output(text), textoutput) - - def test_syntax_error(self): - """Syntax errors should still have fancy special-case formatting""" - textoutput = self._test_external_case("exec ('f(a, b c)')") - self.assertIn(self._as_output( - ' File "", line 1\n' - ' f(a, b c)\n' - + ' ' * self._error_on_character + - ' ^\n' - 'SyntaxError: ' - ), textoutput) - - def test_syntax_error_malformed(self): - """Syntax errors with bogus parameters should break anything""" - textoutput = self._test_external_case("raise SyntaxError(3, 2, 1)") - self.assertIn(self._as_output("\nSyntaxError: "), textoutput) - - def test_syntax_error_import_binary(self): - """Importing a binary file shouldn't break SyntaxError formatting""" - if sys.version_info < (2, 5): - # Python 2.4 assumes the file is latin-1 and tells you off - self._silence_deprecation_warnings() - self._setup_external_case("import bad") - f = open(os.path.join(self.dir, "bad.py"), "wb") - try: - f.write(_b("x\x9c\xcb*\xcd\xcb\x06\x00\x04R\x01\xb9")) - finally: - f.close() - textoutput = self._run_external_case() - matches_error = MatchesAny( - Contains('\nTypeError: '), Contains('\nSyntaxError: ')) - self.assertThat(textoutput, matches_error) - - def test_syntax_error_line_iso_8859_1(self): - """Syntax error on a latin-1 line shows the line decoded""" - text, raw = self._get_sample_text("iso-8859-1") - textoutput = self._setup_external_case("import bad") - self._write_module("bad", "iso-8859-1", - "# coding: iso-8859-1\n! = 0 # %s\n" % text) - textoutput = self._run_external_case() - self.assertIn(self._as_output(_u( - #'bad.py", line 2\n' - ' ! = 0 # %s\n' - ' ^\n' - 'SyntaxError: ') % - (text,)), textoutput) - - def test_syntax_error_line_iso_8859_5(self): - """Syntax error on a iso-8859-5 line shows the line decoded""" - text, raw = self._get_sample_text("iso-8859-5") - textoutput = self._setup_external_case("import bad") - self._write_module("bad", "iso-8859-5", - "# coding: iso-8859-5\n%% = 0 # %s\n" % text) - textoutput = self._run_external_case() - self.assertIn(self._as_output(_u( - #'bad.py", line 2\n' - ' %% = 0 # %s\n' - + ' ' * self._error_on_character + - ' ^\n' - 'SyntaxError: ') % - (text,)), textoutput) - - def test_syntax_error_line_euc_jp(self): - """Syntax error on a euc_jp line shows the line decoded""" - text, raw = self._get_sample_text("euc_jp") - textoutput = self._setup_external_case("import bad") - self._write_module("bad", "euc_jp", - "# coding: euc_jp\n$ = 0 # %s\n" % text) - textoutput = self._run_external_case() - # pypy uses cpython's multibyte codecs so has their behavior here - if self._is_pypy: - self._error_on_character = True - self.assertIn(self._as_output(_u( - #'bad.py", line 2\n' - ' $ = 0 # %s\n' - + ' ' * self._error_on_character + - ' ^\n' - 'SyntaxError: ') % - (text,)), textoutput) - - def test_syntax_error_line_utf_8(self): - """Syntax error on a utf-8 line shows the line decoded""" - text, raw = self._get_sample_text("utf-8") - textoutput = self._setup_external_case("import bad") - self._write_module("bad", "utf-8", _u("\ufeff^ = 0 # %s\n") % text) - textoutput = self._run_external_case() - self.assertIn(self._as_output(_u( - 'bad.py", line 1\n' - ' ^ = 0 # %s\n' - + ' ' * self._error_on_character + - ' ^\n' - 'SyntaxError: ') % - text), textoutput) - - -class TestNonAsciiResultsWithUnittest(TestNonAsciiResults): - """Test that running under unittest produces clean ascii strings""" - - def _run(self, stream, test): - from unittest import TextTestRunner as _Runner - return _Runner(stream).run(test) - - def _as_output(self, text): - if str_is_unicode: - return text - return text.encode("utf-8") - - -class TestDetailsToStr(TestCase): - - def test_no_details(self): - string = _details_to_str({}) - self.assertThat(string, Equals('')) - - def test_binary_content(self): - content = content_from_stream( - StringIO('foo'), content_type=ContentType('image', 'jpeg')) - string = _details_to_str({'attachment': content}) - self.assertThat( - string, Equals("""\ -Binary content: - attachment (image/jpeg) -""")) - - def test_single_line_content(self): - content = text_content('foo') - string = _details_to_str({'attachment': content}) - self.assertThat(string, Equals('attachment: {{{foo}}}\n')) - - def test_multi_line_text_content(self): - content = text_content('foo\nbar\nbaz') - string = _details_to_str({'attachment': content}) - self.assertThat(string, Equals('attachment: {{{\nfoo\nbar\nbaz\n}}}\n')) - - def test_special_text_content(self): - content = text_content('foo') - string = _details_to_str({'attachment': content}, special='attachment') - self.assertThat(string, Equals('foo\n')) - - def test_multiple_text_content(self): - string = _details_to_str( - {'attachment': text_content('foo\nfoo'), - 'attachment-1': text_content('bar\nbar')}) - self.assertThat( - string, Equals('attachment: {{{\n' - 'foo\n' - 'foo\n' - '}}}\n' - '\n' - 'attachment-1: {{{\n' - 'bar\n' - 'bar\n' - '}}}\n')) - - def test_empty_attachment(self): - string = _details_to_str({'attachment': text_content('')}) - self.assertThat( - string, Equals("""\ -Empty attachments: - attachment -""")) - - def test_lots_of_different_attachments(self): - jpg = lambda x: content_from_stream( - StringIO(x), ContentType('image', 'jpeg')) - attachments = { - 'attachment': text_content('foo'), - 'attachment-1': text_content('traceback'), - 'attachment-2': jpg('pic1'), - 'attachment-3': text_content('bar'), - 'attachment-4': text_content(''), - 'attachment-5': jpg('pic2'), - } - string = _details_to_str(attachments, special='attachment-1') - self.assertThat( - string, Equals("""\ -Binary content: - attachment-2 (image/jpeg) - attachment-5 (image/jpeg) -Empty attachments: - attachment-4 - -attachment: {{{foo}}} -attachment-3: {{{bar}}} - -traceback -""")) - - -class TestByTestResultTests(TestCase): - - def setUp(self): - super(TestByTestResultTests, self).setUp() - self.log = [] - self.result = TestByTestResult(self.on_test) - now = iter(range(5)) - self.result._now = lambda: advance_iterator(now) - - def assertCalled(self, **kwargs): - defaults = { - 'test': self, - 'tags': set(), - 'details': None, - 'start_time': 0, - 'stop_time': 1, - } - defaults.update(kwargs) - self.assertEqual([defaults], self.log) - - def on_test(self, **kwargs): - self.log.append(kwargs) - - def test_no_tests_nothing_reported(self): - self.result.startTestRun() - self.result.stopTestRun() - self.assertEqual([], self.log) - - def test_add_success(self): - self.result.startTest(self) - self.result.addSuccess(self) - self.result.stopTest(self) - self.assertCalled(status='success') - - def test_add_success_details(self): - self.result.startTest(self) - details = {'foo': 'bar'} - self.result.addSuccess(self, details=details) - self.result.stopTest(self) - self.assertCalled(status='success', details=details) - - def test_global_tags(self): - self.result.tags(['foo'], []) - self.result.startTest(self) - self.result.addSuccess(self) - self.result.stopTest(self) - self.assertCalled(status='success', tags=set(['foo'])) - - def test_local_tags(self): - self.result.tags(['foo'], []) - self.result.startTest(self) - self.result.tags(['bar'], []) - self.result.addSuccess(self) - self.result.stopTest(self) - self.assertCalled(status='success', tags=set(['foo', 'bar'])) - - def test_add_error(self): - self.result.startTest(self) - try: - 1/0 - except ZeroDivisionError: - error = sys.exc_info() - self.result.addError(self, error) - self.result.stopTest(self) - self.assertCalled( - status='error', - details={'traceback': TracebackContent(error, self)}) - - def test_add_error_details(self): - self.result.startTest(self) - details = {"foo": text_content("bar")} - self.result.addError(self, details=details) - self.result.stopTest(self) - self.assertCalled(status='error', details=details) - - def test_add_failure(self): - self.result.startTest(self) - try: - self.fail("intentional failure") - except self.failureException: - failure = sys.exc_info() - self.result.addFailure(self, failure) - self.result.stopTest(self) - self.assertCalled( - status='failure', - details={'traceback': TracebackContent(failure, self)}) - - def test_add_failure_details(self): - self.result.startTest(self) - details = {"foo": text_content("bar")} - self.result.addFailure(self, details=details) - self.result.stopTest(self) - self.assertCalled(status='failure', details=details) - - def test_add_xfail(self): - self.result.startTest(self) - try: - 1/0 - except ZeroDivisionError: - error = sys.exc_info() - self.result.addExpectedFailure(self, error) - self.result.stopTest(self) - self.assertCalled( - status='xfail', - details={'traceback': TracebackContent(error, self)}) - - def test_add_xfail_details(self): - self.result.startTest(self) - details = {"foo": text_content("bar")} - self.result.addExpectedFailure(self, details=details) - self.result.stopTest(self) - self.assertCalled(status='xfail', details=details) - - def test_add_unexpected_success(self): - self.result.startTest(self) - details = {'foo': 'bar'} - self.result.addUnexpectedSuccess(self, details=details) - self.result.stopTest(self) - self.assertCalled(status='success', details=details) - - def test_add_skip_reason(self): - self.result.startTest(self) - reason = self.getUniqueString() - self.result.addSkip(self, reason) - self.result.stopTest(self) - self.assertCalled( - status='skip', details={'reason': text_content(reason)}) - - def test_add_skip_details(self): - self.result.startTest(self) - details = {'foo': 'bar'} - self.result.addSkip(self, details=details) - self.result.stopTest(self) - self.assertCalled(status='skip', details=details) - - def test_twice(self): - self.result.startTest(self) - self.result.addSuccess(self, details={'foo': 'bar'}) - self.result.stopTest(self) - self.result.startTest(self) - self.result.addSuccess(self) - self.result.stopTest(self) - self.assertEqual( - [{'test': self, - 'status': 'success', - 'start_time': 0, - 'stop_time': 1, - 'tags': set(), - 'details': {'foo': 'bar'}}, - {'test': self, - 'status': 'success', - 'start_time': 2, - 'stop_time': 3, - 'tags': set(), - 'details': None}, - ], - self.log) - - -class TestTagger(TestCase): - - def test_tags_tests(self): - result = ExtendedTestResult() - tagger = Tagger(result, set(['foo']), set(['bar'])) - test1, test2 = self, make_test() - tagger.startTest(test1) - tagger.addSuccess(test1) - tagger.stopTest(test1) - tagger.startTest(test2) - tagger.addSuccess(test2) - tagger.stopTest(test2) - self.assertEqual( - [('startTest', test1), - ('tags', set(['foo']), set(['bar'])), - ('addSuccess', test1), - ('stopTest', test1), - ('startTest', test2), - ('tags', set(['foo']), set(['bar'])), - ('addSuccess', test2), - ('stopTest', test2), - ], result._events) - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/test_testsuite.py b/lib/testtools/testtools/tests/test_testsuite.py deleted file mode 100644 index 3fc837c701d..00000000000 --- a/lib/testtools/testtools/tests/test_testsuite.py +++ /dev/null @@ -1,128 +0,0 @@ -# Copyright (c) 2009-2011 testtools developers. See LICENSE for details. - -"""Test ConcurrentTestSuite and related things.""" - -__metaclass__ = type - -import unittest - -from testtools import ( - ConcurrentTestSuite, - iterate_tests, - PlaceHolder, - TestCase, - ) -from testtools.helpers import try_import -from testtools.testsuite import FixtureSuite, iterate_tests, sorted_tests -from testtools.tests.helpers import LoggingResult - -FunctionFixture = try_import('fixtures.FunctionFixture') - -class Sample(TestCase): - def __hash__(self): - return id(self) - def test_method1(self): - pass - def test_method2(self): - pass - -class TestConcurrentTestSuiteRun(TestCase): - - def test_trivial(self): - log = [] - result = LoggingResult(log) - test1 = Sample('test_method1') - test2 = Sample('test_method2') - original_suite = unittest.TestSuite([test1, test2]) - suite = ConcurrentTestSuite(original_suite, self.split_suite) - suite.run(result) - # log[0] is the timestamp for the first test starting. - test1 = log[1][1] - test2 = log[-1][1] - self.assertIsInstance(test1, Sample) - self.assertIsInstance(test2, Sample) - self.assertNotEqual(test1.id(), test2.id()) - - def test_wrap_result(self): - # ConcurrentTestSuite has a hook for wrapping the per-thread result. - wrap_log = [] - - def wrap_result(thread_safe_result, thread_number): - wrap_log.append( - (thread_safe_result.result.decorated, thread_number)) - return thread_safe_result - - result_log = [] - result = LoggingResult(result_log) - test1 = Sample('test_method1') - test2 = Sample('test_method2') - original_suite = unittest.TestSuite([test1, test2]) - suite = ConcurrentTestSuite( - original_suite, self.split_suite, wrap_result=wrap_result) - suite.run(result) - self.assertEqual( - [(result, 0), - (result, 1), - ], wrap_log) - # Smoke test to make sure everything ran OK. - self.assertNotEqual([], result_log) - - def split_suite(self, suite): - tests = list(iterate_tests(suite)) - return tests[0], tests[1] - - -class TestFixtureSuite(TestCase): - - def setUp(self): - super(TestFixtureSuite, self).setUp() - if FunctionFixture is None: - self.skip("Need fixtures") - - def test_fixture_suite(self): - log = [] - class Sample(TestCase): - def test_one(self): - log.append(1) - def test_two(self): - log.append(2) - fixture = FunctionFixture( - lambda: log.append('setUp'), - lambda fixture: log.append('tearDown')) - suite = FixtureSuite(fixture, [Sample('test_one'), Sample('test_two')]) - suite.run(LoggingResult([])) - self.assertEqual(['setUp', 1, 2, 'tearDown'], log) - - -class TestSortedTests(TestCase): - - def test_sorts_custom_suites(self): - a = PlaceHolder('a') - b = PlaceHolder('b') - class Subclass(unittest.TestSuite): - def sort_tests(self): - self._tests = sorted_tests(self, True) - input_suite = Subclass([b, a]) - suite = sorted_tests(input_suite) - self.assertEqual([a, b], list(iterate_tests(suite))) - self.assertEqual([input_suite], list(iter(suite))) - - def test_custom_suite_without_sort_tests_works(self): - a = PlaceHolder('a') - b = PlaceHolder('b') - class Subclass(unittest.TestSuite):pass - input_suite = Subclass([b, a]) - suite = sorted_tests(input_suite) - self.assertEqual([b, a], list(iterate_tests(suite))) - self.assertEqual([input_suite], list(iter(suite))) - - def test_sorts_simple_suites(self): - a = PlaceHolder('a') - b = PlaceHolder('b') - suite = sorted_tests(unittest.TestSuite([b, a])) - self.assertEqual([a, b], list(iterate_tests(suite))) - - -def test_suite(): - from unittest import TestLoader - return TestLoader().loadTestsFromName(__name__) diff --git a/lib/testtools/testtools/tests/test_with_with.py b/lib/testtools/testtools/tests/test_with_with.py deleted file mode 100644 index e06adeb1816..00000000000 --- a/lib/testtools/testtools/tests/test_with_with.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright (c) 2011 testtools developers. See LICENSE for details. - -from __future__ import with_statement - -import sys - -from testtools import ( - ExpectedException, - TestCase, - ) -from testtools.matchers import ( - AfterPreprocessing, - Equals, - ) - - -class TestExpectedException(TestCase): - """Test the ExpectedException context manager.""" - - def test_pass_on_raise(self): - with ExpectedException(ValueError, 'tes.'): - raise ValueError('test') - - def test_pass_on_raise_matcher(self): - with ExpectedException( - ValueError, AfterPreprocessing(str, Equals('test'))): - raise ValueError('test') - - def test_raise_on_text_mismatch(self): - try: - with ExpectedException(ValueError, 'tes.'): - raise ValueError('mismatch') - except AssertionError: - e = sys.exc_info()[1] - self.assertEqual("'mismatch' does not match /tes./", str(e)) - else: - self.fail('AssertionError not raised.') - - def test_raise_on_general_mismatch(self): - matcher = AfterPreprocessing(str, Equals('test')) - value_error = ValueError('mismatch') - try: - with ExpectedException(ValueError, matcher): - raise value_error - except AssertionError: - e = sys.exc_info()[1] - self.assertEqual(matcher.match(value_error).describe(), str(e)) - else: - self.fail('AssertionError not raised.') - - def test_raise_on_error_mismatch(self): - try: - with ExpectedException(TypeError, 'tes.'): - raise ValueError('mismatch') - except ValueError: - e = sys.exc_info()[1] - self.assertEqual('mismatch', str(e)) - else: - self.fail('ValueError not raised.') - - def test_raise_if_no_exception(self): - try: - with ExpectedException(TypeError, 'tes.'): - pass - except AssertionError: - e = sys.exc_info()[1] - self.assertEqual('TypeError not raised.', str(e)) - else: - self.fail('AssertionError not raised.') - - def test_pass_on_raise_any_message(self): - with ExpectedException(ValueError): - raise ValueError('whatever') diff --git a/lib/testtools/testtools/testsuite.py b/lib/testtools/testtools/testsuite.py deleted file mode 100644 index 67ace561104..00000000000 --- a/lib/testtools/testtools/testsuite.py +++ /dev/null @@ -1,154 +0,0 @@ -# Copyright (c) 2009-2011 testtools developers. See LICENSE for details. - -"""Test suites and related things.""" - -__metaclass__ = type -__all__ = [ - 'ConcurrentTestSuite', - 'iterate_tests', - 'sorted_tests', - ] - -from testtools.helpers import safe_hasattr, try_imports - -Queue = try_imports(['Queue.Queue', 'queue.Queue']) - -import threading -import unittest - -import testtools - - -def iterate_tests(test_suite_or_case): - """Iterate through all of the test cases in 'test_suite_or_case'.""" - try: - suite = iter(test_suite_or_case) - except TypeError: - yield test_suite_or_case - else: - for test in suite: - for subtest in iterate_tests(test): - yield subtest - - -class ConcurrentTestSuite(unittest.TestSuite): - """A TestSuite whose run() calls out to a concurrency strategy.""" - - def __init__(self, suite, make_tests, wrap_result=None): - """Create a ConcurrentTestSuite to execute suite. - - :param suite: A suite to run concurrently. - :param make_tests: A helper function to split the tests in the - ConcurrentTestSuite into some number of concurrently executing - sub-suites. make_tests must take a suite, and return an iterable - of TestCase-like object, each of which must have a run(result) - method. - :param wrap_result: An optional function that takes a thread-safe - result and a thread number and must return a ``TestResult`` - object. If not provided, then ``ConcurrentTestSuite`` will just - use a ``ThreadsafeForwardingResult`` wrapped around the result - passed to ``run()``. - """ - super(ConcurrentTestSuite, self).__init__([suite]) - self.make_tests = make_tests - if wrap_result: - self._wrap_result = wrap_result - - def _wrap_result(self, thread_safe_result, thread_number): - """Wrap a thread-safe result before sending it test results. - - You can either override this in a subclass or pass your own - ``wrap_result`` in to the constructor. The latter is preferred. - """ - return thread_safe_result - - def run(self, result): - """Run the tests concurrently. - - This calls out to the provided make_tests helper, and then serialises - the results so that result only sees activity from one TestCase at - a time. - - ConcurrentTestSuite provides no special mechanism to stop the tests - returned by make_tests, it is up to the make_tests to honour the - shouldStop attribute on the result object they are run with, which will - be set if an exception is raised in the thread which - ConcurrentTestSuite.run is called in. - """ - tests = self.make_tests(self) - try: - threads = {} - queue = Queue() - semaphore = threading.Semaphore(1) - for i, test in enumerate(tests): - process_result = self._wrap_result( - testtools.ThreadsafeForwardingResult(result, semaphore), i) - reader_thread = threading.Thread( - target=self._run_test, args=(test, process_result, queue)) - threads[test] = reader_thread, process_result - reader_thread.start() - while threads: - finished_test = queue.get() - threads[finished_test][0].join() - del threads[finished_test] - except: - for thread, process_result in threads.values(): - process_result.stop() - raise - - def _run_test(self, test, process_result, queue): - try: - test.run(process_result) - finally: - queue.put(test) - - -class FixtureSuite(unittest.TestSuite): - - def __init__(self, fixture, tests): - super(FixtureSuite, self).__init__(tests) - self._fixture = fixture - - def run(self, result): - self._fixture.setUp() - try: - super(FixtureSuite, self).run(result) - finally: - self._fixture.cleanUp() - - def sort_tests(self): - self._tests = sorted_tests(self, True) - - -def _flatten_tests(suite_or_case, unpack_outer=False): - try: - tests = iter(suite_or_case) - except TypeError: - # Not iterable, assume it's a test case. - return [(suite_or_case.id(), suite_or_case)] - if (type(suite_or_case) in (unittest.TestSuite,) or - unpack_outer): - # Plain old test suite (or any others we may add). - result = [] - for test in tests: - # Recurse to flatten. - result.extend(_flatten_tests(test)) - return result - else: - # Find any old actual test and grab its id. - suite_id = None - tests = iterate_tests(suite_or_case) - for test in tests: - suite_id = test.id() - break - # If it has a sort_tests method, call that. - if safe_hasattr(suite_or_case, 'sort_tests'): - suite_or_case.sort_tests() - return [(suite_id, suite_or_case)] - - -def sorted_tests(suite_or_case, unpack_outer=False): - """Sort suite_or_case while preserving non-vanilla TestSuites.""" - tests = _flatten_tests(suite_or_case, unpack_outer=unpack_outer) - tests.sort() - return unittest.TestSuite([test for (sort_key, test) in tests]) diff --git a/lib/testtools/testtools/utils.py b/lib/testtools/testtools/utils.py deleted file mode 100644 index 0f39d8f5b6e..00000000000 --- a/lib/testtools/testtools/utils.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2008-2010 testtools developers. See LICENSE for details. - -"""Utilities for dealing with stuff in unittest. - -Legacy - deprecated - use testtools.testsuite.iterate_tests -""" - -import warnings -warnings.warn("Please import iterate_tests from testtools.testsuite - " - "testtools.utils is deprecated.", DeprecationWarning, stacklevel=2) - -from testtools.testsuite import iterate_tests - diff --git a/lib/update-external.sh b/lib/update-external.sh index 00f1d97e6b1..e9e027994a1 100755 --- a/lib/update-external.sh +++ b/lib/update-external.sh @@ -9,11 +9,6 @@ THIRD_PARTY_DIR="`dirname $0`/../third_party" LIBDIR="`dirname $0`" WORKDIR="`mktemp -d`" -echo "Updating testtools..." -git clone git://github.com/testing-cabal/testtools "$WORKDIR/testtools" -rm -rf "$WORKDIR/testtools/.git" -rsync -avz --delete "$WORKDIR/testtools/" "$LIBDIR/testtools/" - echo "Updating dnspython..." git clone git://www.dnspython.org/dnspython.git "$WORKDIR/dnspython" rm -rf "$WORKDIR/dnspython/.git" diff --git a/lib/wscript_build b/lib/wscript_build index 7e8693bd394..dd74022b8ec 100644 --- a/lib/wscript_build +++ b/lib/wscript_build @@ -4,7 +4,6 @@ import os, Options # work out what python external libraries we need to install external_libs = { - "testtools": "testtools/testtools", "extras": "extras/extras", "mimeparse": "mimeparse/mimeparse", }