From 13a0ed000f4b5e423869fa6a63a7540be17ab375 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Fri, 11 May 2018 22:24:28 -0400 Subject: [PATCH 01/43] expectedFailure should be treated as a TODO. (#75) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * expectedFailure should be treated as a TODO. This aligns better with the specification. > These tests represent a feature to be implemented or a bug to be fixed and act as something of an executable “things to do” list. They are not expected to succeed. Fixes #74 * Update the release docs. * Try to fix brew. --- .travis.yml | 1 + docs/releases.rst | 6 ++++++ tap/runner.py | 4 ++-- tap/tests/test_result.py | 6 ++++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3e01a1f..de124bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,7 @@ matrix: language: generic env: TOX_ENV=py27 before_install: + - brew update - brew upgrade python - python3 -m venv venv - source venv/bin/activate diff --git a/docs/releases.rst b/docs/releases.rst index 323874b..4eeb635 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -1,6 +1,12 @@ Releases ======== +Version 2.3, To Be Released +--------------------------- + +* `unittest.expectedFailure` now uses a TODO directive to better align + with the specification. + Version 2.2, Released January 7, 2018 ------------------------------------- diff --git a/tap/runner.py b/tap/runner.py index 0324961..eb077c2 100644 --- a/tap/runner.py +++ b/tap/runner.py @@ -50,12 +50,12 @@ def addExpectedFailure(self, test, err): diagnostics = formatter.format_exception(err) self.tracker.add_not_ok( self._cls_name(test), self._description(test), - _('(expected failure)'), diagnostics=diagnostics) + 'TODO {}'.format(_('(expected failure)')), diagnostics=diagnostics) def addUnexpectedSuccess(self, test): super(TAPTestResult, self).addUnexpectedSuccess(test) self.tracker.add_ok(self._cls_name(test), self._description(test), - _('(unexpected success)')) + 'TODO {}'.format(_('(unexpected success)'))) def _cls_name(self, test): return test.__class__.__name__ diff --git a/tap/tests/test_result.py b/tap/tests/test_result.py index 1d0094e..93b0a57 100644 --- a/tap/tests/test_result.py +++ b/tap/tests/test_result.py @@ -62,11 +62,13 @@ def test_adds_expected_failure(self): result.addExpectedFailure(FakeTestCase(), exc) line = result.tracker._test_cases['FakeTestCase'][0] self.assertFalse(line.ok) - self.assertEqual(line.directive.text, _('(expected failure)')) + self.assertEqual( + line.directive.text, 'TODO {}'.format(_('(expected failure)'))) def test_adds_unexpected_success(self): result = self._make_one() result.addUnexpectedSuccess(FakeTestCase()) line = result.tracker._test_cases['FakeTestCase'][0] self.assertTrue(line.ok) - self.assertEqual(line.directive.text, _('(unexpected success)')) + self.assertEqual( + line.directive.text, 'TODO {}'.format(_('(unexpected success)'))) From 24589120a5a60820e87d6920f9163ed43efc5f36 Mon Sep 17 00:00:00 2001 From: Andrew McNamara Date: Sun, 13 May 2018 17:17:42 -0400 Subject: [PATCH 02/43] Add version 13 support (#70) * initial version of version 13 support * show example of invalid diagnostic * fix yaml end detection; add test * change ModuleNotFoundError to ImportError * remove print statements from tests * change strings to unicode for python2 * change strings to unicode for python2 * add testing of optional modules * fix flake8 errors * fix pep8 for real * limit imports * improve test cases * lingering long line * fix multiline string for 2.7 * ignore ImportError for coverage * add/clean tests Add tests for malformed yaml, empty file, and warning message. Cleaned tests by making more consistent * clean up process of parsing yaml * document support for v13 * remove extra test run * increase version number * clean up last test case * fix tests for py2 and 3 * remove unicode from print statements * Tweak the docs a bit. * AppVeyor doesn't like the with version of NamedTemporaryFile. --- .gitignore | 4 +- .travis.yml | 3 + Pipfile | 3 +- Pipfile.lock | 310 +++++++++++++++++++-------------------- docs/consumers.rst | 18 +++ docs/releases.rst | 2 + tap/__init__.py | 2 +- tap/line.py | 27 +++- tap/parser.py | 84 +++++++++-- tap/tests/test_loader.py | 5 +- tap/tests/test_parser.py | 232 +++++++++++++++++++++++++++-- tox.ini | 10 ++ 12 files changed, 521 insertions(+), 179 deletions(-) diff --git a/.gitignore b/.gitignore index 39ec5ab..7755710 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ pip-log.txt nosetests.xml htmlcov .cache +.pytest_cache # Translations .transifex.ini @@ -38,8 +39,9 @@ htmlcov .project .pydevproject -# Vim +# Dev *.swp +.vscode # TAP *.tap diff --git a/.travis.yml b/.travis.yml index de124bc..3835e8a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,9 @@ matrix: - brew upgrade python - python3 -m venv venv - source venv/bin/activate + - os: linux + python: 3.6 + env: TOX_ENV=with_optional - os: linux python: 2.7 env: TOX_ENV=runner diff --git a/Pipfile b/Pipfile index 0b7bf1d..8499c0f 100644 --- a/Pipfile +++ b/Pipfile @@ -16,7 +16,8 @@ Sphinx = "*" tox = "*" twine = "*" pytest = "*" - +more-itertools = "*" +pyyaml = "*" [packages] diff --git a/Pipfile.lock b/Pipfile.lock index 85019cc..bbc729b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,20 +1,7 @@ { "_meta": { "hash": { - "sha256": "f8c87d38088c4cd0c495b52eeeb100c09fa7d526e11f4ed07d203b6391bd7bed" - }, - "host-environment-markers": { - "implementation_name": "cpython", - "implementation_version": "3.6.2", - "os_name": "posix", - "platform_machine": "x86_64", - "platform_python_implementation": "CPython", - "platform_release": "15.6.0", - "platform_system": "Darwin", - "platform_version": "Darwin Kernel Version 15.6.0: Mon Nov 13 21:58:35 PST 2017; root:xnu-3248.72.11~1/RELEASE_X86_64", - "python_full_version": "3.6.2", - "python_version": "3.6", - "sys_platform": "darwin" + "sha256": "ab46296ff06c5478af07e61bc3f32d5688d7917c73249df8c89e6413e5473d3a" }, "pipfile-spec": 6, "requires": {}, @@ -37,130 +24,104 @@ }, "attrs": { "hashes": [ - "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450", - "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9" + "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9", + "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450" ], "version": "==17.4.0" }, "babel": { "hashes": [ - "sha256:f20b2acd44f587988ff185d8949c3e208b4b3d5d20fcab7d91fe481ffa435528", - "sha256:6007daf714d0cd5524bbe436e2d42b3c20e68da66289559341e48d2cd6d25811" + "sha256:8ce4cb6fdd4393edd323227cba3a077bceb2a6ce5201c902c65e730046f41f14", + "sha256:ad209a68d7162c4cff4b29cdebe3dec4cef75492df501b0049a9433c96ce6f80" ], - "version": "==2.5.1" + "index": "pypi", + "version": "==2.5.3" }, "certifi": { "hashes": [ - "sha256:244be0d93b71e93fc0a0a479862051414d0e00e16435707e5bf5000f92e04694", - "sha256:5ec74291ca1136b40f0379e1128ff80e866597e4e2c1e755739a913bbc3613c0" + "sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296", + "sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d" ], - "version": "==2017.11.5" + "version": "==2018.1.18" }, "chardet": { "hashes": [ - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae" + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" ], "version": "==3.0.4" }, - "configparser": { - "hashes": [ - "sha256:5308b47021bc2340965c371f0f058cc6971a04502638d4244225c49d80db273a" - ], - "markers": "python_version < '3.2'", - "version": "==3.5.0" - }, "coverage": { "hashes": [ - "sha256:d1ee76f560c3c3e8faada866a07a32485445e16ed2206ac8378bd90dadffb9f0", - "sha256:007eeef7e23f9473622f7d94a3e029a45d55a92a1f083f0f3512f5ab9a669b05", - "sha256:17307429935f96c986a1b1674f78079528833410750321d22b5fb35d1883828e", - "sha256:845fddf89dca1e94abe168760a38271abfc2e31863fbb4ada7f9a99337d7c3dc", - "sha256:3f4d0b3403d3e110d2588c275540649b1841725f5a11a7162620224155d00ba2", - "sha256:4c4f368ffe1c2e7602359c2c50233269f3abe1c48ca6b288dcd0fb1d1c679733", - "sha256:f8c55dd0f56d3d618dfacf129e010cbe5d5f94b6951c1b2f13ab1a2f79c284da", - "sha256:cdd92dd9471e624cd1d8c1a2703d25f114b59b736b0f1f659a98414e535ffb3d", - "sha256:2ad357d12971e77360034c1596011a03f50c0f9e1ecd12e081342b8d1aee2236", - "sha256:e9a0e1caed2a52f15c96507ab78a48f346c05681a49c5b003172f8073da6aa6b", - "sha256:eea9135432428d3ca7ee9be86af27cb8e56243f73764a9b6c3e0bda1394916be", - "sha256:700d7579995044dc724847560b78ac786f0ca292867447afda7727a6fbaa082e", - "sha256:66f393e10dd866be267deb3feca39babba08ae13763e0fc7a1063cbe1f8e49f6", - "sha256:5ff16548492e8a12e65ff3d55857ccd818584ed587a6c2898a9ebbe09a880674", - "sha256:d00e29b78ff610d300b2c37049a41234d48ea4f2d2581759ebcf67caaf731c31", - "sha256:87d942863fe74b1c3be83a045996addf1639218c2cb89c5da18c06c0fe3917ea", - "sha256:358d635b1fc22a425444d52f26287ae5aea9e96e254ff3c59c407426f44574f4", - "sha256:81912cfe276e0069dca99e1e4e6be7b06b5fc8342641c6b472cb2fed7de7ae18", - "sha256:079248312838c4c8f3494934ab7382a42d42d5f365f0cf7516f938dbb3f53f3f", - "sha256:b0059630ca5c6b297690a6bf57bf2fdac1395c24b7935fd73ee64190276b743b", - "sha256:493082f104b5ca920e97a485913de254cbe351900deed72d4264571c73464cd0", - "sha256:e3ba9b14607c23623cf38f90b23f5bed4a3be87cbfa96e2e9f4eabb975d1e98b", - "sha256:82cbd3317320aa63c65555aa4894bf33a13fb3a77f079059eb5935eea415938d", - "sha256:9721f1b7275d3112dc7ccf63f0553c769f09b5c25a26ee45872c7f5c09edf6c1", - "sha256:bd4800e32b4c8d99c3a2c943f1ac430cbf80658d884123d19639bcde90dad44a", - "sha256:f29841e865590af72c4b90d7b5b8e93fd560f5dea436c1d5ee8053788f9285de", - "sha256:f3a5c6d054c531536a83521c00e5d4004f1e126e2e2556ce399bef4180fbe540", - "sha256:dd707a21332615108b736ef0b8513d3edaf12d2a7d5fc26cd04a169a8ae9b526", - "sha256:2e1a5c6adebb93c3b175103c2f855eda957283c10cf937d791d81bef8872d6ca", - "sha256:f87f522bde5540d8a4b11df80058281ac38c44b13ce29ced1e294963dd51a8f8", - "sha256:a7cfaebd8f24c2b537fa6a271229b051cdac9c1734bb6f939ccfc7c055689baa", - "sha256:309d91bd7a35063ec7a0e4d75645488bfab3f0b66373e7722f23da7f5b0f34cc", - "sha256:0388c12539372bb92d6dde68b4627f0300d948965bbb7fc104924d715fdc0965", - "sha256:ab3508df9a92c1d3362343d235420d08e2662969b83134f8a97dc1451cbe5e84", - "sha256:43a155eb76025c61fc20c3d03b89ca28efa6f5be572ab6110b2fb68eda96bfea", - "sha256:f98b461cb59f117887aa634a66022c0bd394278245ed51189f63a036516e32de", - "sha256:b6cebae1502ce5b87d7c6f532fa90ab345cfbda62b95aeea4e431e164d498a3d", - "sha256:a4497faa4f1c0fc365ba05eaecfb6b5d24e3c8c72e95938f9524e29dadb15e76", - "sha256:2b4d7f03a8a6632598cbc5df15bbca9f778c43db7cf1a838f4fa2c8599a8691a", - "sha256:1afccd7e27cac1b9617be8c769f6d8a6d363699c9b86820f40c74cfb3328921c" - ], - "version": "==4.4.2" + "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", + "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", + "sha256:104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a", + "sha256:15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd", + "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", + "sha256:1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2", + "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", + "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", + "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", + "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", + "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", + "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", + "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", + "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", + "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", + "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", + "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", + "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", + "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", + "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", + "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", + "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", + "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", + "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", + "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", + "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", + "sha256:9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4", + "sha256:ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91", + "sha256:b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d", + "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", + "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", + "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", + "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", + "sha256:e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77", + "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80", + "sha256:f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e" + ], + "index": "pypi", + "version": "==4.5.1" }, "docutils": { "hashes": [ - "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6", "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", - "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274" + "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274", + "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6" ], "version": "==0.14" }, - "enum34": { - "hashes": [ - "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", - "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", - "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1", - "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850" - ], - "markers": "python_version < '3.4'", - "version": "==1.1.6" - }, "flake8": { "hashes": [ - "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37", - "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0" + "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0", + "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37" ], + "index": "pypi", "version": "==3.5.0" }, - "funcsigs": { - "hashes": [ - "sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", - "sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50" - ], - "markers": "python_version < '3.3'", - "version": "==1.0.2" - }, "idna": { "hashes": [ - "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", - "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f" + "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", + "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4" ], "version": "==2.6" }, "imagesize": { "hashes": [ - "sha256:6ebdc9e0ad188f9d1b2cdd9bc59cbe42bf931875e829e7a595e6b3abdc05cdfb", - "sha256:0ab2c62b87987e3252f89d30b7cedbec12a01af9274af9ffa48108f2c13c6062" + "sha256:3620cc0cadba3f7475f9940d22431fc4d407269f1be59ec9b8edcca26440cf18", + "sha256:5b326e4678b6925158ccc66a9fa3122b6106d7c876ee32d7de6ce59385b96315" ], - "version": "==0.7.1" + "version": "==1.0.0" }, "jinja2": { "hashes": [ @@ -187,21 +148,38 @@ "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1", "sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba" ], + "index": "pypi", "version": "==2.0.0" }, + "more-itertools": { + "hashes": [ + "sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea", + "sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e", + "sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44" + ], + "index": "pypi", + "version": "==4.1.0" + }, + "packaging": { + "hashes": [ + "sha256:e9215d2d2535d3ae866c3d6efc77d5b24a0192cce0ff20e42896cc0664f889c0", + "sha256:f019b770dd64e585a99714f1fd5e01c7a8f11b45635aa953fd41c689a657375b" + ], + "version": "==17.1" + }, "pbr": { "hashes": [ - "sha256:60c25b7dfd054ef9bb0ae327af949dd4676aa09ac3a9471cdc871d8a9213f9ac", - "sha256:05f61c71aaefc02d8e37c0a3eeb9815ff526ea28b3b76324769e6158d7f95be1" + "sha256:4e8a0ed6a8705a26768f4c3da26026013b157821fe5f95881599556ea9d91c19", + "sha256:dae4aaa78eafcad10ce2581fc34d694faa616727837fd8e55c1a00951ad6744f" ], - "version": "==3.1.1" + "version": "==4.0.2" }, "pkginfo": { "hashes": [ - "sha256:31a49103180ae1518b65d3f4ce09c784e2bc54e338197668b4fb7dc539521024", - "sha256:bb1a6aeabfc898f5df124e7e00303a5b3ec9a489535f346bfbddb081af93f89e" + "sha256:5878d542a4b3f237e359926384f1dde4e099c9f5525d236b1840cf704fa8d474", + "sha256:a39076cb3eb34c333a0dd390b568e9e1e881c7bf2cc0aee12120636816f55aee" ], - "version": "==1.4.1" + "version": "==1.4.2" }, "pluggy": { "hashes": [ @@ -211,15 +189,17 @@ }, "py": { "hashes": [ - "sha256:8cca5c229d225f8c1e3085be4fcf306090b00850fefad892f9d96c7b6e2f310f", - "sha256:ca18943e28235417756316bfada6cd96b23ce60dd532642690dcfdaba988a76d" + "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881", + "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a" ], - "version": "==1.5.2" + "version": "==1.5.3" }, "pycodestyle": { "hashes": [ - "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9", - "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766" + "sha256:1ec08a51c901dfe44921576ed6e4c1f5b7ecbad403f871397feedb5eb8e4fa14", + "sha256:5ff2fbcbab997895ba9ead77e1b38b3ebc2e5c3b8a6194ef918666e4c790a00e", + "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766", + "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9" ], "version": "==2.3.1" }, @@ -237,32 +217,59 @@ ], "version": "==2.2.0" }, + "pyparsing": { + "hashes": [ + "sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04", + "sha256:281683241b25fe9b80ec9d66017485f6deff1af5cde372469134b56ca8447a07", + "sha256:8f1e18d3fd36c6795bb7e02a39fd05c611ffc2596c1e0d995d34d67630426c18", + "sha256:9e8143a3e15c13713506886badd96ca4b579a87fbdf49e550dbfc057d6cb218e", + "sha256:b8b3117ed9bdf45e14dcc89345ce638ec7e0e29b2b579fa1ecf32ce45ebac8a5", + "sha256:e4d45427c6e20a59bf4f88c639dcc03ce30d193112047f94012102f235853a58", + "sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010" + ], + "version": "==2.2.0" + }, "pytest": { "hashes": [ - "sha256:b84878865558194630c6147f44bdaef27222a9f153bbd4a08908b16bf285e0b1", - "sha256:53548280ede7818f4dc2ad96608b9f08ae2cc2ca3874f2ceb6f97e3583f25bc4" + "sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c", + "sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1" ], - "version": "==3.3.2" + "index": "pypi", + "version": "==3.5.0" }, "pytz": { "hashes": [ - "sha256:80af0f3008046b9975242012a985f04c5df1f01eed4ec1633d56cc47a75a6a48", - "sha256:feb2365914948b8620347784b6b6da356f31c9d03560259070b2f30cff3d469d", - "sha256:59707844a9825589878236ff2f4e0dc9958511b7ffaae94dc615da07d4a68d33", - "sha256:d0ef5ef55ed3d37854320d4926b04a4cb42a2e88f71da9ddfdacfde8e364f027", - "sha256:c41c62827ce9cafacd6f2f7018e4f83a6f1986e87bfd000b8cfbd4ab5da95f1a", - "sha256:8cc90340159b5d7ced6f2ba77694d946fc975b09f1a51d93f3ce3bb399396f94", - "sha256:dd2e4ca6ce3785c8dd342d1853dd9052b19290d5bf66060846e5dc6b8d6667f7", - "sha256:699d18a2a56f19ee5698ab1123bbcc1d269d061996aeb1eda6d89248d3542b82", - "sha256:fae4cffc040921b8a2d60c6cf0b5d662c1190fe54d718271db4eb17d44a185b7" + "sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555", + "sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749" ], - "version": "==2017.3" + "version": "==2018.4" + }, + "pyyaml": { + "hashes": [ + "sha256:0c507b7f74b3d2dd4d1322ec8a94794927305ab4cebbe89cc47fe5e81541e6e8", + "sha256:16b20e970597e051997d90dc2cddc713a2876c47e3d92d59ee198700c5427736", + "sha256:3262c96a1ca437e7e4763e2843746588a965426550f3797a79fca9c6199c431f", + "sha256:326420cbb492172dec84b0f65c80942de6cedb5233c413dd824483989c000608", + "sha256:4474f8ea030b5127225b8894d626bb66c01cda098d47a2b0d3429b6700af9fd8", + "sha256:592766c6303207a20efc445587778322d7f73b161bd994f227adaa341ba212ab", + "sha256:5ac82e411044fb129bae5cfbeb3ba626acb2af31a8d17d175004b70862a741a7", + "sha256:5f84523c076ad14ff5e6c037fe1c89a7f73a3e04cf0377cb4d017014976433f3", + "sha256:827dc04b8fa7d07c44de11fabbc888e627fa8293b695e0f99cb544fdfa1bf0d1", + "sha256:b4c423ab23291d3945ac61346feeb9a0dc4184999ede5e7c43e1ffb975130ae6", + "sha256:bc6bced57f826ca7cb5125a10b23fd0f2fff3b7c4701d64c439a300ce665fff8", + "sha256:c01b880ec30b5a6e6aa67b09a2fe3fb30473008c85cd6a67359a1b15ed6d83a4", + "sha256:ca233c64c6e40eaa6c66ef97058cdc80e8d0157a443655baa1b2966e812807ca", + "sha256:e863072cdf4c72eebf179342c94e6989c67185842d9997960b3e69290b2fa269" + ], + "index": "pypi", + "version": "==3.12" }, "requests": { "hashes": [ "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" ], + "index": "pypi", "version": "==2.18.4" }, "requests-toolbelt": { @@ -274,61 +281,55 @@ }, "six": { "hashes": [ - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb", - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9" + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" ], "version": "==1.11.0" }, "snowballstemmer": { "hashes": [ - "sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89", - "sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128" + "sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128", + "sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89" ], "version": "==1.2.1" }, "sphinx": { "hashes": [ - "sha256:fdf77f4f30d84a314c797d67fe7d1b46665e6c48a25699d7bf0610e05a2221d4", - "sha256:c6de5dbdbb7a0d7d2757f4389cc00e8f6eb3c49e1772378967a12cfcf2cfe098" + "sha256:5a1c9a0fec678c24b9a2f5afba240c04668edb7f45c67ce2ed008996b3f21ae2", + "sha256:7a606d77618a753adb79e13605166e3cf6a0e5678526e044236fc1ac43650910" ], - "version": "==1.6.5" + "index": "pypi", + "version": "==1.7.2" }, "sphinxcontrib-websupport": { "hashes": [ - "sha256:f4932e95869599b89bf4f80fc3989132d83c9faa5bf633e7b5e0c25dffb75da2", - "sha256:7a85961326aa3a400cd4ad3c816d70ed6f7c740acd7ce5d78cd0a67825072eb9" + "sha256:7a85961326aa3a400cd4ad3c816d70ed6f7c740acd7ce5d78cd0a67825072eb9", + "sha256:f4932e95869599b89bf4f80fc3989132d83c9faa5bf633e7b5e0c25dffb75da2" ], "version": "==1.0.1" }, "tox": { "hashes": [ - "sha256:8af30fd835a11f3ff8e95176ccba5a4e60779df4d96a9dfefa1a1704af263225", - "sha256:752f5ec561c6c08c5ecb167d3b20f4f4ffc158c0ab78855701a75f5cef05f4b8" + "sha256:96efa09710a3daeeb845561ebbe1497641d9cef2ee0aea30db6969058b2bda2f", + "sha256:9ee7de958a43806402a38c0d2aa07fa8553f4d2c20a15b140e9f771c2afeade0" ], - "version": "==2.9.1" + "index": "pypi", + "version": "==3.0.0" }, "tqdm": { "hashes": [ - "sha256:4c041f8019f7be65b8028ddde9a836f7ccc51c4637f1ff2ba9b5813d38d19d5a", - "sha256:df32e6f127dc0ccbc675eadb33f749abbcb8f174c5cb9ec49c0cdb73aa737377" + "sha256:059e7dd579f2c1a2b9103a8ec76fb0bc32cc16904d7d65977edfed6b5745dc48", + "sha256:a180389a780f6b52268c30f40fdcb0443ab3d925574579d987eadf10c59ff90c" ], - "version": "==4.19.5" + "version": "==4.22.0" }, "twine": { "hashes": [ - "sha256:d3ce5c480c22ccfb761cd358526e862b32546d2fe4bc93d46b5cf04ea3cc46ca", - "sha256:caa45b7987fc96321258cd7668e3be2ff34064f5c66d2d975b641adca659c1ab" + "sha256:08eb132bbaec40c6d25b358f546ec1dc96ebd2638a86eea68769d9e67fe2b129", + "sha256:2fd9a4d9ff0bcacf41fdc40c8cb0cfaef1f1859457c9653fd1b92237cc4e9f25" ], - "version": "==1.9.1" - }, - "typing": { - "hashes": [ - "sha256:349b1f9c109c84b53ac79ac1d822eaa68fc91d63b321bd9392df15098f746f53", - "sha256:63a8255fe7c6269916baa440eb9b6a67139b0b97a01af632e7bd2842e1e02f15", - "sha256:d514bd84b284dd3e844f0305ac07511f097e325171f6cc4a20878d11ad771849" - ], - "markers": "python_version < '3.5'", - "version": "==3.6.2" + "index": "pypi", + "version": "==1.11.0" }, "urllib3": { "hashes": [ @@ -339,11 +340,10 @@ }, "virtualenv": { "hashes": [ - "sha256:39d88b533b422825d644087a21e78c45cf5af0ef7a99a1fc9fbb7b481e5c85b0", - "sha256:02f8102c2436bb03b3ee6dede1919d1dac8a427541652e5ec95171ec8adbc93a" + "sha256:1d7e241b431e7afce47e77f8843a276f652699d1fa4f93b9d8ce0076fd7b0b54", + "sha256:e8e05d4714a1c51a2f5921e62f547fcb0f713ebbe959e0a7f585cc8bef71d11f" ], - "markers": "python_version != '3.2'", - "version": "==15.1.0" + "version": "==15.2.0" } } } diff --git a/docs/consumers.rst b/docs/consumers.rst index 91389ad..08f614c 100644 --- a/docs/consumers.rst +++ b/docs/consumers.rst @@ -112,6 +112,24 @@ The API specifics are listed below. .. autoclass:: tap.parser.Parser :members: +TAP version 13 +~~~~~~~~~~~~~~ + +The specification for version 13 adds support for `yaml blocks `_ +to provide additional information about the preceding test. In order to consume +yaml blocks, ``tappy`` requires `pyyaml `_ and +`more-itertools `_ to be installed. + +These dependencies are optional. If they are not installed, TAP output will still +be consumed, but any yaml blocks will be parsed as :class:`tap.line.Unknown`. If a +:class:`tap.line.Result` object has an associated yaml block, :attr:`~tap.line.Result.yaml_block` +will return the block converted to a ``dict``. Otherwise, it will return ``None``. + +``tappy`` provides a strict interpretation of the specification. A yaml block will +only be associated with a result if it immediately follows that result. Any +:class:`diagnostic ` between a :class:`result ` and a yaml +block will result in the block lines being parsed as :class:`tap.line.Unknown`. + Line Categories ~~~~~~~~~~~~~~~ diff --git a/docs/releases.rst b/docs/releases.rst index 4eeb635..2eda995 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -4,6 +4,8 @@ Releases Version 2.3, To Be Released --------------------------- +* Make tappy version 13 compliant by adding + support for parsing yaml blocks. * `unittest.expectedFailure` now uses a TODO directive to better align with the specification. diff --git a/tap/__init__.py b/tap/__init__.py index e90a1e8..9894059 100644 --- a/tap/__init__.py +++ b/tap/__init__.py @@ -3,4 +3,4 @@ from .runner import TAPTestRunner __all__ = ['TAPTestRunner'] -__version__ = '2.2' +__version__ = '2.3' diff --git a/tap/line.py b/tap/line.py index b38fa31..352bc1b 100644 --- a/tap/line.py +++ b/tap/line.py @@ -1,4 +1,9 @@ # Copyright (c) 2018, Matt Layman and contributors +try: + import yaml + LOAD_YAML = True +except ImportError: # pragma: no cover + LOAD_YAML = False class Line(object): @@ -16,7 +21,7 @@ class Result(Line): def __init__( self, ok, number=None, description='', directive=None, - diagnostics=None): + diagnostics=None, raw_yaml_block=None): self._ok = ok if number: self._number = int(number) @@ -26,6 +31,7 @@ def __init__( self._description = description self.directive = directive self.diagnostics = diagnostics + self._yaml_block = raw_yaml_block @property def category(self): @@ -69,6 +75,25 @@ def todo(self): """ return self.directive.todo + @property + def yaml_block(self): + """Lazy load a yaml_block. + + If yaml support is not available, + there is an error in parsing the yaml block, + or no yaml is associated with this result, + ``None`` will be returned. + + :rtype: dict + """ + if LOAD_YAML and self._yaml_block is not None: + try: + yaml_dict = yaml.load(self._yaml_block) + return yaml_dict + except yaml.error.YAMLError: + print('Error parsing yaml block. Check formatting.') + return None + def __str__(self): is_not = '' if not self.ok: diff --git a/tap/parser.py b/tap/parser.py index e28872b..16b3e3c 100644 --- a/tap/parser.py +++ b/tap/parser.py @@ -1,6 +1,7 @@ # Copyright (c) 2018, Matt Layman and contributors from io import StringIO +import itertools import re import sys @@ -8,6 +9,13 @@ from tap.i18n import _ from tap.line import Bail, Diagnostic, Plan, Result, Unknown, Version +try: + from more_itertools import peekable + import yaml # noqa + ENABLE_VERSION_13 = True +except ImportError: # pragma: no cover + ENABLE_VERSION_13 = False + class Parser(object): """A parser for TAP files and lines.""" @@ -40,8 +48,14 @@ class Parser(object): """, re.VERBOSE) version = re.compile(r'^TAP version (?P\d+)$') + yaml_block_start = re.compile(r'^(?P\s+)-') + yaml_block_end = re.compile(r'^\s+\.\.\.') + TAP_MINIMUM_DECLARED_VERSION = 13 + def __init__(self): + self._try_peeking = False + def parse_file(self, filename): """Parse a TAP file to an iterable of tap.line.Line objects. @@ -73,18 +87,35 @@ def parse(self, fh): stripped from the input lines. """ with fh: - for line in fh: - yield self.parse_line(line.rstrip()) - - def parse_line(self, text): + try: + first_line = next(fh) + except StopIteration: + return + first_parsed = self.parse_line(first_line.rstrip()) + fh_new = itertools.chain([first_line], fh) + if first_parsed.category == 'version' and \ + first_parsed.version >= 13: + if ENABLE_VERSION_13: + fh_new = peekable(itertools.chain([first_line], fh)) + self._try_peeking = True + else: # pragma no cover + print(""" +WARNING: Optional imports not found, TAP 13 output will be + ignored. To parse yaml, see requirements in docs: + https://tappy.readthedocs.io/en/latest/consumers.html#tap-version-13""") + + for line in fh_new: + yield self.parse_line(line.rstrip(), fh_new) + + def parse_line(self, text, fh=None): """Parse a line into whatever TAP category it belongs.""" match = self.ok.match(text) if match: - return self._parse_result(True, match) + return self._parse_result(True, match, fh) match = self.not_ok.match(text) if match: - return self._parse_result(False, match) + return self._parse_result(False, match, fh) if self.diagnostic.match(text): return Diagnostic(text) @@ -114,11 +145,46 @@ def _parse_plan(self, match): return Plan(expected_tests, directive) - def _parse_result(self, ok, match): + def _parse_result(self, ok, match, fh=None): """Parse a matching result line into a result instance.""" + peek_match = None + try: + if fh is not None and self._try_peeking: + peek_match = self.yaml_block_start.match(fh.peek()) + except StopIteration: + pass + if peek_match is None: + return Result( + ok, + number=match.group('number'), + description=match.group('description').strip(), + directive=Directive(match.group('directive')) + ) + indent = peek_match.group('indent') + concat_yaml = self._extract_yaml_block(indent, fh) return Result( - ok, match.group('number'), match.group('description').strip(), - Directive(match.group('directive'))) + ok, + number=match.group('number'), + description=match.group('description').strip(), + directive=Directive(match.group('directive')), + raw_yaml_block=concat_yaml + ) + + def _extract_yaml_block(self, indent, fh): + """Extract a raw yaml block from a file handler""" + raw_yaml = [] + indent_match = re.compile(r'^{}'.format(indent)) + try: + fh.next() + while indent_match.match(fh.peek()): + raw_yaml.append(fh.next().replace(indent, '', 1)) + # check for the end and stop adding yaml if encountered + if self.yaml_block_end.match(fh.peek()): + fh.next() + break + except StopIteration: + pass + return '\n'.join(raw_yaml) def _parse_version(self, match): version = int(match.group('version')) diff --git a/tap/tests/test_loader.py b/tap/tests/test_loader.py index d90315f..8dcd5e1 100644 --- a/tap/tests/test_loader.py +++ b/tap/tests/test_loader.py @@ -1,6 +1,7 @@ # Copyright (c) 2018, Matt Layman and contributors import inspect +from io import StringIO import os import tempfile import unittest @@ -116,8 +117,8 @@ def test_skip_plan_aborts_loading(self): self.assertEqual( 'Skipping this test file.', suite._tests[0]._line.description) - @mock.patch('tap.parser.sys') - def test_loads_from_stream(self, mock_sys): + @mock.patch('tap.parser.sys.stdin', StringIO(u'')) + def test_loads_from_stream(self): loader = Loader() suite = loader.load_suite_from_stdin() self.assertTrue(isinstance(suite, unittest.TestSuite)) diff --git a/tap/tests/test_parser.py b/tap/tests/test_parser.py index 92e7aa0..4e919d1 100644 --- a/tap/tests/test_parser.py +++ b/tap/tests/test_parser.py @@ -1,6 +1,9 @@ # Copyright (c) 2018, Matt Layman and contributors +from contextlib import contextmanager import inspect +from io import BytesIO, StringIO +import sys import tempfile import unittest @@ -12,6 +15,20 @@ from tap.parser import Parser +@contextmanager +def captured_output(): + if sys.version_info[0] < 3: + new_out, new_err = BytesIO(), BytesIO() + else: + new_out, new_err = StringIO(), StringIO() + old_out, old_err = sys.stdout, sys.stderr + try: + sys.stdout, sys.stderr = new_out, new_err + yield sys.stdout, sys.stderr + finally: + sys.stdout, sys.stderr = old_out, old_err + + class TestParser(unittest.TestCase): """Tests for tap.parser.Parser""" @@ -196,18 +213,215 @@ def test_parses_file(self): self.assertEqual('plan', lines[0].category) self.assertEqual('test', lines[1].category) self.assertTrue(lines[1].ok) + self.assertIsNone(lines[1].yaml_block) self.assertEqual('test', lines[2].category) self.assertFalse(lines[2].ok) - @mock.patch('tap.parser.sys') - def test_parses_stdin(self, mock_sys): - mock_sys.stdin.__iter__.return_value = iter([ - '1..2\n', - 'ok 1 A passing test\n', - 'not ok 2 A failing test\n', - ]) - mock_sys.stdin.__enter__.return_value = None - mock_sys.stdin.__exit__.return_value = None + def test_parses_yaml(self): + sample = inspect.cleandoc( + u"""TAP version 13 + 1..2 + ok 1 A passing test + --- + test: sample yaml + ... + not ok 2 A failing test""") + parser = Parser() + lines = [] + + for line in parser.parse_text(sample): + lines.append(line) + + try: + import yaml + from more_itertools import peekable # noqa + converted_yaml = yaml.load(u"""test: sample yaml""") + self.assertEqual(4, len(lines)) + self.assertEqual(13, lines[0].version) + self.assertEqual(converted_yaml, lines[2].yaml_block) + self.assertEqual('test', lines[3].category) + self.assertIsNone(lines[3].yaml_block) + except ImportError: + self.assertEqual(7, len(lines)) + self.assertEqual(13, lines[0].version) + for l in list(range(3, 6)): + self.assertEqual('unknown', lines[l].category) + self.assertEqual('test', lines[6].category) + + def test_parses_yaml_no_end(self): + sample = inspect.cleandoc( + u"""TAP version 13 + 1..2 + ok 1 A passing test + --- + test: sample yaml + not ok 2 A failing test""") + parser = Parser() + lines = [] + + for line in parser.parse_text(sample): + lines.append(line) + + try: + import yaml + from more_itertools import peekable # noqa + converted_yaml = yaml.load(u"""test: sample yaml""") + self.assertEqual(4, len(lines)) + self.assertEqual(13, lines[0].version) + self.assertEqual(converted_yaml, lines[2].yaml_block) + self.assertEqual('test', lines[3].category) + self.assertIsNone(lines[3].yaml_block) + except ImportError: + self.assertEqual(6, len(lines)) + self.assertEqual(13, lines[0].version) + for l in list(range(3, 5)): + self.assertEqual('unknown', lines[l].category) + self.assertEqual('test', lines[5].category) + + def test_parses_yaml_more_complex(self): + sample = inspect.cleandoc( + u"""TAP version 13 + 1..2 + ok 1 A passing test + --- + message: test + severity: fail + data: + got: + - foo + expect: + - bar""") + parser = Parser() + lines = [] + + for line in parser.parse_text(sample): + lines.append(line) + + try: + import yaml + from more_itertools import peekable # noqa + converted_yaml = yaml.load(u""" + message: test + severity: fail + data: + got: + - foo + expect: + - bar""") + self.assertEqual(3, len(lines)) + self.assertEqual(13, lines[0].version) + self.assertEqual(converted_yaml, lines[2].yaml_block) + except ImportError: + self.assertEqual(11, len(lines)) + self.assertEqual(13, lines[0].version) + for l in list(range(3, 11)): + self.assertEqual('unknown', lines[l].category) + + def test_parses_yaml_no_association(self): + sample = inspect.cleandoc( + u"""TAP version 13 + 1..2 + ok 1 A passing test + # Diagnostic line + --- + test: sample yaml + ... + not ok 2 A failing test""") + parser = Parser() + lines = [] + + for line in parser.parse_text(sample): + lines.append(line) + + self.assertEqual(8, len(lines)) + self.assertEqual(13, lines[0].version) + self.assertIsNone(lines[2].yaml_block) + self.assertEqual('diagnostic', lines[3].category) + for l in list(range(4, 7)): + self.assertEqual('unknown', lines[l].category) + self.assertEqual('test', lines[7].category) + + def test_parses_yaml_no_start(self): + sample = inspect.cleandoc( + u"""TAP version 13 + 1..2 + ok 1 A passing test + test: sample yaml + ... + not ok 2 A failing test""") + parser = Parser() + lines = [] + + for line in parser.parse_text(sample): + lines.append(line) + + self.assertEqual(6, len(lines)) + self.assertEqual(13, lines[0].version) + self.assertIsNone(lines[2].yaml_block) + for l in list(range(3, 5)): + self.assertEqual('unknown', lines[l].category) + self.assertEqual('test', lines[5].category) + + def test_malformed_yaml(self): + self.maxDiff = None + sample = inspect.cleandoc( + u"""TAP version 13 + 1..2 + ok 1 A passing test + --- + test: sample yaml + \tfail: tabs are not allowed! + ... + not ok 2 A failing test""") + yaml_err = inspect.cleandoc( + u""" +WARNING: Optional imports not found, TAP 13 output will be + ignored. To parse yaml, see requirements in docs: + https://tappy.readthedocs.io/en/latest/consumers.html#tap-version-13""") + parser = Parser() + lines = [] + + with captured_output() as (parse_out, _): + for line in parser.parse_text(sample): + lines.append(line) + + try: + import yaml # noqa + from more_itertools import peekable # noqa + self.assertEqual(4, len(lines)) + self.assertEqual(13, lines[0].version) + with captured_output() as (out, _): + self.assertIsNone(lines[2].yaml_block) + self.assertEqual( + 'Error parsing yaml block. Check formatting.', + out.getvalue().strip()) + self.assertEqual('test', lines[3].category) + self.assertIsNone(lines[3].yaml_block) + except ImportError: + self.assertEqual(8, len(lines)) + self.assertEqual(13, lines[0].version) + for l in list(range(3, 7)): + self.assertEqual('unknown', lines[l].category) + self.assertEqual('test', lines[7].category) + self.assertEqual( + yaml_err, parse_out.getvalue().strip()) + + def test_parse_empty_file(self): + temp = tempfile.NamedTemporaryFile(delete=False) + temp.close() + parser = Parser() + lines = [] + + for line in parser.parse_file(temp.name): + lines.append(line) + + self.assertEqual(0, len(lines)) + + @mock.patch('tap.parser.sys.stdin', + StringIO(u"""1..2 +ok 1 A passing test +not ok 2 A failing test""")) + def test_parses_stdin(self): parser = Parser() lines = [] diff --git a/tox.ini b/tox.ini index 1c3d62d..7551011 100644 --- a/tox.ini +++ b/tox.ini @@ -27,6 +27,14 @@ deps = pytest commands = pytest {envsitepackagesdir}/tap +[testenv:with_optional] +deps = + Babel + mock + pyyaml + more-itertools +commands = python tap/tests/run.py + [testenv:runner] deps = Babel @@ -58,6 +66,8 @@ deps = coverage mock codecov + pyyaml + more-itertools commands = coverage run tap/tests/run.py coverage report -m --include "*/tap/*" --omit "*/tests/*" From 356c49c560b271b53ee2d9f83943af06b0dff1f5 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Mon, 14 May 2018 09:43:21 -0400 Subject: [PATCH 03/43] Add an optional method to install TAP version 13 support. (#77) --- README.md | 11 +++++++++++ docs/consumers.rst | 4 +++- docs/index.rst | 10 ++++++++++ docs/releases.rst | 7 ++++--- setup.py | 6 ++++++ 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 74e189a..25878e2 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,17 @@ you only need to install `nose-tap`. $ pip install nose-tap ``` +TAP version 13 brings support +for [YAML blocks](http://testanything.org/tap-version-13-specification.html#yaml-blocks) +associated with test results. +To work with version 13, install the optional dependencies. +Learn more about YAML support +in the [TAP version 13](http://tappy.readthedocs.io/en/latest/consumers.html#tap-version-13) section. + +```bash +$ pip install tap.py[yaml] +``` + Motivation ---------- diff --git a/docs/consumers.rst b/docs/consumers.rst index 08f614c..6f8d645 100644 --- a/docs/consumers.rst +++ b/docs/consumers.rst @@ -112,10 +112,12 @@ The API specifics are listed below. .. autoclass:: tap.parser.Parser :members: +.. _tap-version-13: + TAP version 13 ~~~~~~~~~~~~~~ -The specification for version 13 adds support for `yaml blocks `_ +The specification for TAP version 13 adds support for `yaml blocks `_ to provide additional information about the preceding test. In order to consume yaml blocks, ``tappy`` requires `pyyaml `_ and `more-itertools `_ to be installed. diff --git a/docs/index.rst b/docs/index.rst index deb8818..0a68b16 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -31,6 +31,16 @@ It is continuously tested on Linux, OS X, and Windows. $ pip install tap.py +TAP version 13 brings support for YAML blocks +for `YAML blocks `_ +associated with test results. +To work with version 13, install the optional dependencies. +Learn more about YAML support in the :ref:`tap-version-13` section. + +.. code-block:: console + + $ pip install tap.py[yaml] + Documentation ------------- diff --git a/docs/releases.rst b/docs/releases.rst index 2eda995..da3fea3 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -4,9 +4,10 @@ Releases Version 2.3, To Be Released --------------------------- -* Make tappy version 13 compliant by adding - support for parsing yaml blocks. -* `unittest.expectedFailure` now uses a TODO directive to better align +* Add optional method to install tappy for YAML support + with ``pip install tap.py[yaml]``. +* Make tappy version 13 compliant by adding support for parsing YAML blocks. +* ``unittest.expectedFailure`` now uses a TODO directive to better align with the specification. Version 2.2, Released January 7, 2018 diff --git a/setup.py b/setup.py index c28cd52..73cc80f 100644 --- a/setup.py +++ b/setup.py @@ -62,6 +62,12 @@ def run(self): zip_safe=False, platforms='any', install_requires=[], + extras_require={ + 'yaml': [ + 'more-itertools', + 'PyYAML', + ], + }, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', From ffd27d1d6b93fe894eb666ccc9dbedb52fc59284 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Tue, 15 May 2018 09:24:45 -0400 Subject: [PATCH 04/43] Set 2.3 release date. --- docs/releases.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releases.rst b/docs/releases.rst index da3fea3..65ce051 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -1,8 +1,8 @@ Releases ======== -Version 2.3, To Be Released ---------------------------- +Version 2.3, Released May 15, 2018 +---------------------------------- * Add optional method to install tappy for YAML support with ``pip install tap.py[yaml]``. From 8c0361476f14ce53a0d4d749a1d931fec479621e Mon Sep 17 00:00:00 2001 From: richard-bosworth Date: Tue, 29 May 2018 18:22:50 +0100 Subject: [PATCH 05/43] Output Tappy Version when needed (Issue #78) (#79) * add-tappy-version13-header-to-producer * update-import-of-mock-for-multiple-versions-of-python * add-tap-version-to-streaming-and-small-pr-comments * add-patch-to-test-to-pass-in-any-environment * fix-test --- AUTHORS | 1 + docs/releases.rst | 4 +++ tap/tests/test_tracker.py | 61 +++++++++++++++++++++++++++++++++++++-- tap/tracker.py | 20 +++++++++++++ 4 files changed, 84 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index addfcbc..8b3631f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -10,3 +10,4 @@ Contributors * Matt Layman * Michael F. Lamb (http://datagrok.org) * Nicolas Caniart +* Richard Bosworth diff --git a/docs/releases.rst b/docs/releases.rst index 65ce051..57b52cb 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -1,6 +1,10 @@ Releases ======== +Version 2.4, In Development May 15, 2018 +---------------------------------------- +* Add support for producing version 13 TAP output (streaming and file reports) + Version 2.3, Released May 15, 2018 ---------------------------------- diff --git a/tap/tests/test_tracker.py b/tap/tests/test_tracker.py index 05cdb87..f43b8a7 100644 --- a/tap/tests/test_tracker.py +++ b/tap/tests/test_tracker.py @@ -8,6 +8,11 @@ except ImportError: from io import StringIO +try: + from unittest import mock +except ImportError: + import mock + from tap.i18n import _ from tap.tests import TestCase from tap.tracker import Tracker @@ -87,7 +92,8 @@ def test_individual_report_has_no_plan_when_combined(self): self.assertTrue('Look ma' in report) self.assertFalse('1..' in report) - def test_combined_results_in_one_file(self): + @mock.patch('tap.tracker.ENABLE_VERSION_13', False) + def test_combined_results_in_one_file_tap_version_12(self): outdir = tempfile.mkdtemp() tracker = Tracker(outdir=outdir, combined=True) tracker.add_ok('FakeTestCase', 'YESSS!') @@ -112,6 +118,34 @@ def test_combined_results_in_one_file(self): header_2=self._make_header('DifferentFakeTestCase'))) self.assertEqual(report.strip(), expected) + @mock.patch('tap.tracker.ENABLE_VERSION_13', True) + def test_combined_results_in_one_file_tap_version_13(self): + outdir = tempfile.mkdtemp() + tracker = Tracker(outdir=outdir, combined=True) + tracker.add_ok('FakeTestCase', 'YESSS!') + tracker.add_ok('DifferentFakeTestCase', 'GOAAL!') + + tracker.generate_tap_reports() + + self.assertFalse( + os.path.exists(os.path.join(outdir, 'FakeTestCase.tap'))) + self.assertFalse( + os.path.exists(os.path.join(outdir, 'DifferentFakeTestCase.tap'))) + with open(os.path.join(outdir, 'testresults.tap'), 'r') as f: + report = f.read() + expected = inspect.cleandoc( + """ + TAP version 13 + {header_1} + ok 1 YESSS! + {header_2} + ok 2 GOAAL! + 1..2 + """.format( + header_1=self._make_header('FakeTestCase'), + header_2=self._make_header('DifferentFakeTestCase'))) + self.assertEqual(report.strip(), expected) + def test_tracker_does_not_stream_by_default(self): tracker = Tracker() self.assertFalse(tracker.streaming) @@ -120,6 +154,7 @@ def test_tracker_has_stream(self): tracker = Tracker() self.assertTrue(tracker.stream is None) + @mock.patch('tap.tracker.ENABLE_VERSION_13', False) def test_add_ok_writes_to_stream_while_streaming(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream) @@ -137,6 +172,7 @@ def test_add_ok_writes_to_stream_while_streaming(self): header_2=self._make_header('AnotherTestCase'))) self.assertEqual(stream.getvalue().strip(), expected) + @mock.patch('tap.tracker.ENABLE_VERSION_13', False) def test_add_not_ok_writes_to_stream_while_streaming(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream) @@ -150,6 +186,7 @@ def test_add_not_ok_writes_to_stream_while_streaming(self): header=self._make_header('FakeTestCase'))) self.assertEqual(stream.getvalue().strip(), expected) + @mock.patch('tap.tracker.ENABLE_VERSION_13', False) def test_add_skip_writes_to_stream_while_streaming(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream) @@ -174,6 +211,7 @@ def test_streaming_does_not_write_files(self): self.assertFalse( os.path.exists(os.path.join(outdir, 'FakeTestCase.tap'))) + @mock.patch('tap.tracker.ENABLE_VERSION_13', False) def test_streaming_writes_plan(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream) @@ -183,6 +221,22 @@ def test_streaming_writes_plan(self): self.assertEqual(stream.getvalue(), '1..42\n') + @mock.patch('tap.tracker.ENABLE_VERSION_13', True) + def test_streaming_writes_tap_version_13(self): + stream = StringIO() + tracker = Tracker(streaming=True, stream=stream) + + tracker.add_skip('FakeTestCase', 'YESSS!', 'a reason') + + expected = inspect.cleandoc( + """ + TAP version 13 + {header} + ok 1 YESSS! # SKIP a reason + """.format( + header=self._make_header('FakeTestCase'))) + self.assertEqual(stream.getvalue().strip(), expected) + def test_get_default_tap_file_path(self): tracker = Tracker() file_path = tracker._get_tap_file_path('foo') @@ -208,6 +262,7 @@ def test_header_set_by_init(self): tracker = Tracker(header=False) self.assertFalse(tracker.header) + @mock.patch('tap.tracker.ENABLE_VERSION_13', False) def test_does_not_write_header(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream, header=False) @@ -215,5 +270,7 @@ def test_does_not_write_header(self): tracker.add_skip('FakeTestCase', 'YESSS!', 'a reason') expected = inspect.cleandoc( - """ok 1 YESSS! # SKIP a reason""") + """ + ok 1 YESSS! # SKIP a reason + """) self.assertEqual(stream.getvalue().strip(), expected) diff --git a/tap/tracker.py b/tap/tracker.py index baea295..cae1440 100644 --- a/tap/tracker.py +++ b/tap/tracker.py @@ -9,6 +9,13 @@ from tap.i18n import _ from tap.line import Result +try: + import more_itertools # noqa + import yaml # noqa + ENABLE_VERSION_13 = True +except ImportError: # pragma: no cover + ENABLE_VERSION_13 = False + class Tracker(object): @@ -42,6 +49,9 @@ def __init__( else: # pragma: no cover self._sanitized_table = str.maketrans(' \\/\n', '----') + if self.streaming: + self._write_tap_version(self.stream) + def _get_outdir(self): return self._outdir @@ -118,6 +128,7 @@ def generate_tap_reports(self): if self.outdir: combined_file = os.path.join(self.outdir, combined_file) with open(combined_file, 'w') as out_file: + self._write_tap_version(out_file) for test_case in self.combined_test_cases_seen: self.generate_tap_report( test_case, self._test_cases[test_case], out_file) @@ -126,6 +137,7 @@ def generate_tap_reports(self): else: for test_case, tap_lines in self._test_cases.items(): with open(self._get_tap_file_path(test_case), 'w') as out_file: + self._write_tap_version(out_file) self.generate_tap_report(test_case, tap_lines, out_file) def generate_tap_report(self, test_case, tap_lines, out_file): @@ -139,6 +151,14 @@ def generate_tap_report(self, test_case, tap_lines, out_file): if not self.combined: print('1..{0}'.format(len(tap_lines)), file=out_file) + def _write_tap_version(self, filename): + """Write a Version 13 TAP row + + filename can be a filename or a stream + """ + if ENABLE_VERSION_13: + print('TAP version 13', file=filename) + def _write_test_case_header(self, test_case, stream): print(_('# TAP results for {test_case}').format( test_case=test_case), file=stream) From b25afc64520326fb694dfc63eb26c0d61c5e7801 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Tue, 29 May 2018 13:37:30 -0400 Subject: [PATCH 06/43] Prepare version 2.4. --- docs/releases.rst | 9 ++++++--- tap/__init__.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/releases.rst b/docs/releases.rst index 57b52cb..b593248 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -1,9 +1,12 @@ Releases ======== -Version 2.4, In Development May 15, 2018 ----------------------------------------- -* Add support for producing version 13 TAP output (streaming and file reports) +Version 2.4, Released May 29, 2018 +---------------------------------- + +* Add support for producing TAP version 13 output + to streaming and file reports + by including the ``TAP version 13`` line. Version 2.3, Released May 15, 2018 ---------------------------------- diff --git a/tap/__init__.py b/tap/__init__.py index 9894059..a7ef2ad 100644 --- a/tap/__init__.py +++ b/tap/__init__.py @@ -3,4 +3,4 @@ from .runner import TAPTestRunner __all__ = ['TAPTestRunner'] -__version__ = '2.3' +__version__ = '2.4' From cce53b8de1dccb9fa9fd4eb3b53a4c925c6f5f12 Mon Sep 17 00:00:00 2001 From: meejah Date: Fri, 14 Sep 2018 17:31:51 -0600 Subject: [PATCH 07/43] Write the plan line up front in streaming mode (#80) * write the plan line up front in streaming mode This adds a .set_plan() method on Tracker so that streaming reporters can report how many tests they expect. If this method is NOT called, legacy behavior is preserved (the plan is written out to the stream at the end) * Add contribution and release-notes * tests for both streaming and non-streaming .plan usage * better comments * write plan first if set_plan was called * nicer docstring * error if set_plan called when not combined or streaming * unused variables * whitespace * unit-test when plan= is provided to Tracker * flake8 --- AUTHORS | 1 + docs/releases.rst | 7 ++++++ tap/tests/test_tracker.py | 31 +++++++++++++++++++++++++ tap/tracker.py | 48 +++++++++++++++++++++++++++++++++++---- 4 files changed, 82 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 8b3631f..566404e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -8,6 +8,7 @@ Contributors * Marc Abramowitz * Mark E. Hamilton * Matt Layman +* meejah (https://meejah.ca) * Michael F. Lamb (http://datagrok.org) * Nicolas Caniart * Richard Bosworth diff --git a/docs/releases.rst b/docs/releases.rst index b593248..6018d81 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -1,6 +1,13 @@ Releases ======== +Version 2.5, In Development +--------------------------- + +* Add `set_plan` to `Tracker` which allows producing the `1..N` line + before any tests. + + Version 2.4, Released May 29, 2018 ---------------------------------- diff --git a/tap/tests/test_tracker.py b/tap/tests/test_tracker.py index f43b8a7..9bc4ae5 100644 --- a/tap/tests/test_tracker.py +++ b/tap/tests/test_tracker.py @@ -221,6 +221,37 @@ def test_streaming_writes_plan(self): self.assertEqual(stream.getvalue(), '1..42\n') + @mock.patch('tap.tracker.ENABLE_VERSION_13', False) + def test_write_plan_first_streaming(self): + stream = StringIO() + tracker = Tracker(streaming=True, stream=stream) + tracker.set_plan(123) + tracker.generate_tap_reports() + self.assertEqual(stream.getvalue(), '1..123\n') + + @mock.patch('tap.tracker.ENABLE_VERSION_13', False) + def test_write_plan_immediate_streaming(self): + stream = StringIO() + Tracker(streaming=True, stream=stream, plan=123) + self.assertEqual(stream.getvalue(), '1..123\n') + + @mock.patch('tap.tracker.ENABLE_VERSION_13', False) + def test_write_plan_first_combined(self): + outdir = tempfile.mkdtemp() + tracker = Tracker(streaming=False, outdir=outdir, combined=True) + tracker.set_plan(123) + tracker.generate_tap_reports() + with open(os.path.join(outdir, "testresults.tap"), "r") as f: + lines = f.readlines() + self.assertEqual(lines[0], '1..123\n') + + @mock.patch('tap.tracker.ENABLE_VERSION_13', False) + def test_write_plan_first_not_combined(self): + outdir = tempfile.mkdtemp() + tracker = Tracker(streaming=False, outdir=outdir, combined=False) + with self.assertRaises(ValueError): + tracker.set_plan(123) + @mock.patch('tap.tracker.ENABLE_VERSION_13', True) def test_streaming_writes_tap_version_13(self): stream = StringIO() diff --git a/tap/tracker.py b/tap/tracker.py index cae1440..693856e 100644 --- a/tap/tracker.py +++ b/tap/tracker.py @@ -21,7 +21,7 @@ class Tracker(object): def __init__( self, outdir=None, combined=False, streaming=False, stream=None, - header=True): + header=True, plan=None): self.outdir = outdir # Combine all the test results into one file. @@ -36,6 +36,9 @@ def __init__( # Stream output directly to a stream instead of file output. self.streaming = streaming self.stream = stream + # The total number of tests we expect (or None if we don't know yet). + self.plan = plan + self._plan_written = False # Display the test case header unless told not to. self.header = header @@ -51,6 +54,8 @@ def __init__( if self.streaming: self._write_tap_version(self.stream) + if self.plan is not None: + self._write_plan(self.stream) def _get_outdir(self): return self._outdir @@ -112,15 +117,32 @@ def _get_next_line_number(self, class_name): # case may not be tracked yet. In that case, the line is 1. return 1 + def set_plan(self, total): + """Notify the tracker how many total tests there will be""" + self.plan = total + if self.streaming: + # this will only write the plan if we haven't written it + # already .. but we want to check if we already wrote a + # test out (in which case we can't just write the plan out + # right here..) + if not self.combined_test_cases_seen: + self._write_plan(self.stream) + elif not self.combined: + raise ValueError( + "set_plan can only be used with combined or streaming output" + ) + def generate_tap_reports(self): """Generate TAP reports. The results are either combined into a single output file or the output file name is generated from the test case. """ - if self.streaming: - # The results already went to the stream, record the plan. + # We're streaming but set_plan wasn't called, so we can only + # know the plan now (at the end) + if self.streaming and not self._plan_written: print('1..{0}'.format(self.combined_line_number), file=self.stream) + self._plan_written = True return if self.combined: @@ -129,11 +151,16 @@ def generate_tap_reports(self): combined_file = os.path.join(self.outdir, combined_file) with open(combined_file, 'w') as out_file: self._write_tap_version(out_file) + if self.plan is not None: + print('1..{0}'.format(self.plan), file=out_file) for test_case in self.combined_test_cases_seen: self.generate_tap_report( test_case, self._test_cases[test_case], out_file) - print( - '1..{0}'.format(self.combined_line_number), file=out_file) + if self.plan is None: + print( + '1..{0}'.format(self.combined_line_number), + file=out_file, + ) else: for test_case, tap_lines in self._test_cases.items(): with open(self._get_tap_file_path(test_case), 'w') as out_file: @@ -159,6 +186,17 @@ def _write_tap_version(self, filename): if ENABLE_VERSION_13: print('TAP version 13', file=filename) + def _write_plan(self, stream): + """Write the plan line to the stream + + If we have a plan and have not yet written it out, write it to + the given stream + """ + if self.plan is not None: + if not self._plan_written: + print('1..{0}'.format(self.plan), file=stream) + self._plan_written = True + def _write_test_case_header(self, test_case, stream): print(_('# TAP results for {test_case}').format( test_case=test_case), file=stream) From b406b4eb5eebdd9cdc09cddfbf4f628145b1e0cf Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Fri, 14 Sep 2018 19:57:57 -0400 Subject: [PATCH 08/43] Use Black. --- .travis.yml | 32 +---- Pipfile | 5 +- Pipfile.lock | 255 +++++++++++++++++++----------------- docs/contributing.rst | 9 +- docs/releases.rst | 1 + setup.cfg | 3 + setup.py | 74 ++++------- tap/__init__.py | 4 +- tap/adapter.py | 5 +- tap/directive.py | 14 +- tap/formatter.py | 4 +- tap/i18n.py | 4 +- tap/line.py | 42 +++--- tap/loader.py | 14 +- tap/main.py | 29 ++-- tap/parser.py | 73 ++++++----- tap/rules.py | 60 ++++----- tap/runner.py | 44 ++++--- tap/tests/factory.py | 18 ++- tap/tests/run.py | 6 +- tap/tests/test_adapter.py | 21 ++- tap/tests/test_directive.py | 12 +- tap/tests/test_formatter.py | 9 +- tap/tests/test_line.py | 24 ++-- tap/tests/test_loader.py | 51 ++++---- tap/tests/test_main.py | 18 +-- tap/tests/test_parser.py | 178 ++++++++++++++----------- tap/tests/test_result.py | 24 ++-- tap/tests/test_rules.py | 40 +++--- tap/tests/test_runner.py | 3 +- tap/tests/test_tracker.py | 168 +++++++++++++----------- tap/tests/testcase.py | 3 +- tap/tracker.py | 95 ++++++++------ tox.ini | 9 +- transifex.py | 49 +++---- 35 files changed, 724 insertions(+), 676 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3835e8a..0c969c9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,42 +32,14 @@ matrix: python: 2.7 env: TOX_ENV=runner - os: linux - python: 2.7 - env: TOX_ENV=flake8 + python: 3.7 + env: TOX_ENV=lint - os: linux python: 2.7 env: TOX_ENV=integration - os: linux python: 2.7 env: TOX_ENV=coverage - # Stop testing the languages until someone steps up to support them. - # - os: linux - # python: 2.7 - # env: TOX_ENV=language_ar - # - os: linux - # python: 2.7 - # env: TOX_ENV=language_de - # - os: linux - # python: 2.7 - # env: TOX_ENV=language_es - # - os: linux - # python: 2.7 - # env: TOX_ENV=language_fr - # - os: linux - # python: 2.7 - # env: TOX_ENV=language_it - # - os: linux - # python: 2.7 - # env: TOX_ENV=language_ja - # - os: linux - # python: 2.7 - # env: TOX_ENV=language_nl - # - os: linux - # python: 2.7 - # env: TOX_ENV=language_pt - # - os: linux - # python: 2.7 - # env: TOX_ENV=language_ru install: - pip install Babel tox script: tox -e $TOX_ENV diff --git a/Pipfile b/Pipfile index 8499c0f..807e908 100644 --- a/Pipfile +++ b/Pipfile @@ -1,12 +1,9 @@ [[source]] - url = "https://pypi.python.org/simple" verify_ssl = true name = "pypi" - [dev-packages] - Babel = "*" coverage = "*" "flake8" = "*" @@ -18,6 +15,6 @@ twine = "*" pytest = "*" more-itertools = "*" pyyaml = "*" +black = "*" [packages] - diff --git a/Pipfile.lock b/Pipfile.lock index bbc729b..21f88e3 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ab46296ff06c5478af07e61bc3f32d5688d7917c73249df8c89e6413e5473d3a" + "sha256": "75d157b7787ee1eff10691e6cbbd12b52687ef68f2aa6509d18f8a829f166beb" }, "pipfile-spec": 6, "requires": {}, @@ -17,32 +17,55 @@ "develop": { "alabaster": { "hashes": [ - "sha256:2eef172f44e8d301d25aff8068fddd65f767a3f04b5f15b0f4922f113aa1c732", - "sha256:37cdcb9e9954ed60912ebc1ca12a9d12178c26637abdf124e3cde2341c257fe0" + "sha256:674bb3bab080f598371f4443c5008cbfeb1a5e622dd312395d2d82af2c54c456", + "sha256:b63b1f4dc77c074d386752ec4a8a7517600f6c0db8cd42980cae17ab7b3275d7" ], - "version": "==0.7.10" + "version": "==0.7.11" + }, + "appdirs": { + "hashes": [ + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" + ], + "version": "==1.4.3" + }, + "atomicwrites": { + "hashes": [ + "sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", + "sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee" + ], + "markers": "python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'", + "version": "==1.2.1" }, "attrs": { "hashes": [ - "sha256:1c7960ccfd6a005cd9f7ba884e6316b5e430a3f1a6c37c5f87d8b43f83b54ec9", - "sha256:a17a9573a6f475c99b551c0e0a812707ddda1ec9653bed04c13841404ed6f450" + "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", + "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" ], - "version": "==17.4.0" + "version": "==18.2.0" }, "babel": { "hashes": [ - "sha256:8ce4cb6fdd4393edd323227cba3a077bceb2a6ce5201c902c65e730046f41f14", - "sha256:ad209a68d7162c4cff4b29cdebe3dec4cef75492df501b0049a9433c96ce6f80" + "sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669", + "sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23" ], "index": "pypi", - "version": "==2.5.3" + "version": "==2.6.0" + }, + "black": { + "hashes": [ + "sha256:22158b89c1a6b4eb333a1e65e791a3f8b998cf3b11ae094adb2570f31f769a44", + "sha256:4b475bbd528acce094c503a3d2dbc2d05a4075f6d0ef7d9e7514518e14cc5191" + ], + "index": "pypi", + "version": "==18.6b4" }, "certifi": { "hashes": [ - "sha256:14131608ad2fd56836d33a71ee60fa1c82bc9d2c8d98b7bdbc631fe1b3cd1296", - "sha256:edbc3f203427eef571f79a7692bb160a2b0f7ccaa31953e99bd17e307cf63f7d" + "sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638", + "sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a" ], - "version": "==2018.1.18" + "version": "==2018.8.24" }, "chardet": { "hashes": [ @@ -51,47 +74,45 @@ ], "version": "==3.0.4" }, + "click": { + "hashes": [ + "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", + "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" + ], + "version": "==6.7" + }, "coverage": { "hashes": [ - "sha256:03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", - "sha256:0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", - "sha256:104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a", - "sha256:15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd", - "sha256:198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", - "sha256:1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2", - "sha256:28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", - "sha256:2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", - "sha256:337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", - "sha256:3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", - "sha256:3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", - "sha256:3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", - "sha256:3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", - "sha256:4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", - "sha256:56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", - "sha256:5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", - "sha256:69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", - "sha256:6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", - "sha256:701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", - "sha256:7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", - "sha256:76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", - "sha256:7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", - "sha256:7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", - "sha256:7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", - "sha256:8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", - "sha256:9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", - "sha256:9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4", - "sha256:ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91", - "sha256:b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d", - "sha256:be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", - "sha256:c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", - "sha256:de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", - "sha256:e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", - "sha256:e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77", - "sha256:f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80", - "sha256:f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e" + "sha256:0dcf381f51f589f1f797449602a7fe4e63be8a7963c259c13742af3f30be902e", + "sha256:11a4bb30306def2fa012e3429de44a93ef2ae3b6ad3f6b800f6c578658a5c402", + "sha256:166c957a38b034050a14201f64eec11fc95e17bf2ba31fc07d887db82bae1a47", + "sha256:184e6680f85fcc1b371f67ab732290ecf96a225448198e14ec170986db47b0aa", + "sha256:1904deb72c561a8e445feb190db07ca4b165ee85567894b4b85fdb9bf21a27c0", + "sha256:1f2003b83426cfaadebff8b9bb1fb3650134a15fda3a81434cc8415896d7a7bc", + "sha256:1f462997b1804f8b5d1ee2b262626fc76b746e66023eb64f529af35991167c7c", + "sha256:213697f49eba45b5fb05e77f63bdb7c0d13eed12dcd08e6af43224615b28b524", + "sha256:2557da232b0daeb55afe2f7e55f7b80c56bfa2981864c6638b32b5691da9f4c3", + "sha256:395a8525f1456439a5d6c248bc1397040491047e3e0e0c4ceb2059155419cd3b", + "sha256:43d6334b35e50e74d034ec075ffd9082c559bca624924af6c7e9d2b8aef0f362", + "sha256:4566c74bde36aaaef0372fb11678edf43dcc73f4eb8dbb6987250658c4a3b95a", + "sha256:6d39cc527c9c7a30f20bed14b5cf9a7e87ef1f3528c1847d1c81caf75a31ebb6", + "sha256:8bd69d3cba21d885df6fe8728cee779a722da08cf84072558956c148b5ab61e5", + "sha256:a1d0fcbbe0735eb66c6622266b12e60ea8d37ada405cb8f73b154c5eec467187", + "sha256:ab706bfbb365f232be01a536a9199ee6bfc80c9b63fb7825fdd5f4ae5cc2a12c", + "sha256:afbf4cee68d2f2968b06951cf16c0b18513eb59bb3af0685084de6cacb04e217", + "sha256:bbc8913cd5889df7eab597a4b4074a2c6c5ee6ca9aad58a9ba0f3f847b1a99df", + "sha256:bd5428ab378a7432e43afa52b6bb9c5d48f5029f395a97dc9ebf87fc0f2a9d8b", + "sha256:c3efe0185583443e04f8519818f4772d92fbbdf5f9fa23165f2f2482b20efc37", + "sha256:d40277e918da575d008e2955a0ca6600f870bdb3570b07ee3a754ea9301862e7", + "sha256:d4b6ec6951e20ea3f5d1fefe35b4bcbf692d4306f1b932c28dd2ee4cb167152c", + "sha256:d5837e813ad62c856bc80f988c4e24e0d2b7b22a8a1dad8c1cfcb8ff4d4750a8", + "sha256:d9583ae0e152c5fb0142cb55c3a11e1b13006c00d0c3e8b35ccc2d4ebfc6645e", + "sha256:e27380cbe4088a1df514e75aa4fe6dc9e98bbd7902cf28ab16e8b2de0f8cb344", + "sha256:e624daef32f8808296312e72190c7e576852cb75c27935b31c1bbbde14ab353c", + "sha256:ef4278e5ac1e47c731ec5e3e48351721e01d2eb4fefa9b97fcdba7495a82cfad" ], "index": "pypi", - "version": "==4.5.1" + "version": "==5.0a2" }, "docutils": { "hashes": [ @@ -111,17 +132,18 @@ }, "idna": { "hashes": [ - "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", - "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4" + "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", + "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" ], - "version": "==2.6" + "version": "==2.7" }, "imagesize": { "hashes": [ - "sha256:3620cc0cadba3f7475f9940d22431fc4d407269f1be59ec9b8edcca26440cf18", - "sha256:5b326e4678b6925158ccc66a9fa3122b6106d7c876ee32d7de6ce59385b96315" + "sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", + "sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5" ], - "version": "==1.0.0" + "markers": "python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'", + "version": "==1.1.0" }, "jinja2": { "hashes": [ @@ -153,12 +175,12 @@ }, "more-itertools": { "hashes": [ - "sha256:0dd8f72eeab0d2c3bd489025bb2f6a1b8342f9b198f6fc37b52d15cfa4531fea", - "sha256:11a625025954c20145b37ff6309cd54e39ca94f72f6bb9576d1195db6fa2442e", - "sha256:c9ce7eccdcb901a2c75d326ea134e0886abfbea5f93e91cc95de9507c0816c44" + "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092", + "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e", + "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d" ], "index": "pypi", - "version": "==4.1.0" + "version": "==4.3.0" }, "packaging": { "hashes": [ @@ -169,10 +191,10 @@ }, "pbr": { "hashes": [ - "sha256:4e8a0ed6a8705a26768f4c3da26026013b157821fe5f95881599556ea9d91c19", - "sha256:dae4aaa78eafcad10ce2581fc34d694faa616727837fd8e55c1a00951ad6744f" + "sha256:1b8be50d938c9bb75d0eaf7eda111eec1bf6dc88a62a6412e33bf077457e0f45", + "sha256:b486975c0cafb6beeb50ca0e17ba047647f229087bd74e37f4a7e2cac17d2caa" ], - "version": "==4.0.2" + "version": "==4.2.0" }, "pkginfo": { "hashes": [ @@ -183,21 +205,22 @@ }, "pluggy": { "hashes": [ - "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff" + "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", + "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1" ], - "version": "==0.6.0" + "markers": "python_version != '3.3.*' and python_version != '3.2.*' and python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.0.*'", + "version": "==0.7.1" }, "py": { "hashes": [ - "sha256:29c9fab495d7528e80ba1e343b958684f4ace687327e6f789a94bf3d1915f881", - "sha256:983f77f3331356039fdd792e9220b7b8ee1aa6bd2b25f567a963ff1de5a64f6a" + "sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1", + "sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6" ], - "version": "==1.5.3" + "markers": "python_version != '3.3.*' and python_version != '3.2.*' and python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.0.*'", + "version": "==1.6.0" }, "pycodestyle": { "hashes": [ - "sha256:1ec08a51c901dfe44921576ed6e4c1f5b7ecbad403f871397feedb5eb8e4fa14", - "sha256:5ff2fbcbab997895ba9ead77e1b38b3ebc2e5c3b8a6194ef918666e4c790a00e", "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766", "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9" ], @@ -220,63 +243,50 @@ "pyparsing": { "hashes": [ "sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04", - "sha256:281683241b25fe9b80ec9d66017485f6deff1af5cde372469134b56ca8447a07", - "sha256:8f1e18d3fd36c6795bb7e02a39fd05c611ffc2596c1e0d995d34d67630426c18", - "sha256:9e8143a3e15c13713506886badd96ca4b579a87fbdf49e550dbfc057d6cb218e", - "sha256:b8b3117ed9bdf45e14dcc89345ce638ec7e0e29b2b579fa1ecf32ce45ebac8a5", - "sha256:e4d45427c6e20a59bf4f88c639dcc03ce30d193112047f94012102f235853a58", "sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010" ], "version": "==2.2.0" }, "pytest": { "hashes": [ - "sha256:6266f87ab64692112e5477eba395cfedda53b1933ccd29478e671e73b420c19c", - "sha256:fae491d1874f199537fd5872b5e1f0e74a009b979df9d53d1553fd03da1703e1" + "sha256:453cbbbe5ce6db38717d282b758b917de84802af4288910c12442984bde7b823", + "sha256:a8a07f84e680482eb51e244370aaf2caa6301ef265f37c2bdefb3dd3b663f99d" ], "index": "pypi", - "version": "==3.5.0" + "version": "==3.8.0" }, "pytz": { "hashes": [ - "sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555", - "sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749" + "sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053", + "sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277" ], - "version": "==2018.4" + "version": "==2018.5" }, "pyyaml": { "hashes": [ - "sha256:0c507b7f74b3d2dd4d1322ec8a94794927305ab4cebbe89cc47fe5e81541e6e8", - "sha256:16b20e970597e051997d90dc2cddc713a2876c47e3d92d59ee198700c5427736", - "sha256:3262c96a1ca437e7e4763e2843746588a965426550f3797a79fca9c6199c431f", - "sha256:326420cbb492172dec84b0f65c80942de6cedb5233c413dd824483989c000608", - "sha256:4474f8ea030b5127225b8894d626bb66c01cda098d47a2b0d3429b6700af9fd8", - "sha256:592766c6303207a20efc445587778322d7f73b161bd994f227adaa341ba212ab", - "sha256:5ac82e411044fb129bae5cfbeb3ba626acb2af31a8d17d175004b70862a741a7", - "sha256:5f84523c076ad14ff5e6c037fe1c89a7f73a3e04cf0377cb4d017014976433f3", - "sha256:827dc04b8fa7d07c44de11fabbc888e627fa8293b695e0f99cb544fdfa1bf0d1", - "sha256:b4c423ab23291d3945ac61346feeb9a0dc4184999ede5e7c43e1ffb975130ae6", - "sha256:bc6bced57f826ca7cb5125a10b23fd0f2fff3b7c4701d64c439a300ce665fff8", - "sha256:c01b880ec30b5a6e6aa67b09a2fe3fb30473008c85cd6a67359a1b15ed6d83a4", - "sha256:ca233c64c6e40eaa6c66ef97058cdc80e8d0157a443655baa1b2966e812807ca", - "sha256:e863072cdf4c72eebf179342c94e6989c67185842d9997960b3e69290b2fa269" + "sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb", + "sha256:3108529b78577327d15eec243f0ff348a0640b0c3478d67ad7f5648f93bac3e2", + "sha256:3c17fb92c8ba2f525e4b5f7941d850e7a48c3a59b32d331e2502a3cdc6648e76", + "sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b", + "sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b" ], "index": "pypi", - "version": "==3.12" + "version": "==4.2b4" }, "requests": { "hashes": [ - "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", - "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" + "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", + "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" ], "index": "pypi", - "version": "==2.18.4" + "version": "==2.19.1" }, "requests-toolbelt": { "hashes": [ "sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237", "sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5" ], + "markers": "python_version != '3.3.*' and python_version != '3.2.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version >= '2.6' and python_version < '4'", "version": "==0.8.0" }, "six": { @@ -295,33 +305,42 @@ }, "sphinx": { "hashes": [ - "sha256:5a1c9a0fec678c24b9a2f5afba240c04668edb7f45c67ce2ed008996b3f21ae2", - "sha256:7a606d77618a753adb79e13605166e3cf6a0e5678526e044236fc1ac43650910" + "sha256:95acd6648902333647a0e0564abdb28a74b0a76d2333148aa35e5ed1f56d3c4b", + "sha256:c091dbdd5cc5aac6eb95d591a819fd18bccec90ffb048ec465b165a48b839b45" ], "index": "pypi", - "version": "==1.7.2" + "version": "==1.8.0" }, "sphinxcontrib-websupport": { "hashes": [ - "sha256:7a85961326aa3a400cd4ad3c816d70ed6f7c740acd7ce5d78cd0a67825072eb9", - "sha256:f4932e95869599b89bf4f80fc3989132d83c9faa5bf633e7b5e0c25dffb75da2" + "sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd", + "sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9" + ], + "markers": "python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'", + "version": "==1.1.0" + }, + "toml": { + "hashes": [ + "sha256:380178cde50a6a79f9d2cf6f42a62a5174febe5eea4126fe4038785f1d888d42", + "sha256:a7901919d3e4f92ffba7ff40a9d697e35bbbc8a8049fe8da742f34c83606d957" ], - "version": "==1.0.1" + "version": "==0.9.6" }, "tox": { "hashes": [ - "sha256:96efa09710a3daeeb845561ebbe1497641d9cef2ee0aea30db6969058b2bda2f", - "sha256:9ee7de958a43806402a38c0d2aa07fa8553f4d2c20a15b140e9f771c2afeade0" + "sha256:433bb93c57edae263150767e672a0d468ab4fefcc1958eb4013e56a670bb851e", + "sha256:bfb4e4efb7c61a54bc010a5c00fdbe0973bc4bdf04090bfcd3c93c901006177c" ], "index": "pypi", - "version": "==3.0.0" + "version": "==3.3.0" }, "tqdm": { "hashes": [ - "sha256:059e7dd579f2c1a2b9103a8ec76fb0bc32cc16904d7d65977edfed6b5745dc48", - "sha256:a180389a780f6b52268c30f40fdcb0443ab3d925574579d987eadf10c59ff90c" + "sha256:18f1818ce951aeb9ea162ae1098b43f583f7d057b34d706f66939353d1208889", + "sha256:df02c0650160986bac0218bb07952245fc6960d23654648b5d5526ad5a4128c9" ], - "version": "==4.22.0" + "markers": "python_version != '3.1.*' and python_version >= '2.6' and python_version != '3.0.*'", + "version": "==4.26.0" }, "twine": { "hashes": [ @@ -333,17 +352,19 @@ }, "urllib3": { "hashes": [ - "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", - "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" + "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", + "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" ], - "version": "==1.22" + "markers": "python_version != '3.3.*' and python_version != '3.2.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version >= '2.6' and python_version < '4'", + "version": "==1.23" }, "virtualenv": { "hashes": [ - "sha256:1d7e241b431e7afce47e77f8843a276f652699d1fa4f93b9d8ce0076fd7b0b54", - "sha256:e8e05d4714a1c51a2f5921e62f547fcb0f713ebbe959e0a7f585cc8bef71d11f" + "sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669", + "sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752" ], - "version": "==15.2.0" + "markers": "python_version != '3.2.*' and python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.0.*'", + "version": "==16.0.0" } } } diff --git a/docs/contributing.rst b/docs/contributing.rst index d9fb2d2..3a7d174 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -39,6 +39,9 @@ you should be ready to develop. Guidelines ---------- -1. Code should follow PEP 8 style. Please run it through ``pep8`` to check. -2. Please try to conform with any conventions seen in the code for consistency. -3. Make sure your change works against master! (Bonus points for unit tests.) +1. Code uses Black style. Please run it through ``black tap`` to autoformat. +2. Make sure your change works against master! (Bonus points for unit tests.) +3. Document your change in the ``docs/releases.rst`` file. +4. For first time contributors, please add your name to ``AUTHORS`` + so you get attribution for you effort. + This is also to recognize your claim to the copyright in the project. diff --git a/docs/releases.rst b/docs/releases.rst index 6018d81..e435c46 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -6,6 +6,7 @@ Version 2.5, In Development * Add `set_plan` to `Tracker` which allows producing the `1..N` line before any tests. +* Switch code style to use Black formatting. Version 2.4, Released May 29, 2018 diff --git a/setup.cfg b/setup.cfg index 24fa24b..21dc8f9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,5 +10,8 @@ input_dirs = tap output_file = tap/locale/tappy.pot copyright_holder = Matt Layman +[flake8] +max-line-length = 88 + [metadata] license-file = LICENSE diff --git a/setup.py b/setup.py index 73cc80f..83188c4 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ class BuildPy(build_py): """Custom ``build_py`` command to always build mo files for wheels.""" def run(self): - self.run_command('compile_catalog') + self.run_command("compile_catalog") # build_py is an old style class so super cannot be used. build_py.run(self) @@ -29,68 +29,52 @@ class Sdist(sdist): """Custom ``sdist`` command to ensure that mo files are always created.""" def run(self): - self.run_command('compile_catalog') + self.run_command("compile_catalog") # sdist is an old style class so super cannot be used. sdist.run(self) # The docs import setup.py for the version so only call setup when not behaving # as a module. -if __name__ == '__main__': - with open('docs/releases.rst', 'r') as f: +if __name__ == "__main__": + with open("docs/releases.rst", "r") as f: releases = f.read() - long_description = __doc__ + '\n\n' + releases + long_description = __doc__ + "\n\n" + releases setup( - name='tap.py', + name="tap.py", version=tap.__version__, - url='https://github.com/python-tap/tappy', - license='BSD', - author='Matt Layman', - author_email='matthewlayman@gmail.com', - description='Test Anything Protocol (TAP) tools', + url="https://github.com/python-tap/tappy", + license="BSD", + author="Matt Layman", + author_email="matthewlayman@gmail.com", + description="Test Anything Protocol (TAP) tools", long_description=long_description, packages=find_packages(), entry_points={ - 'console_scripts': [ - 'tappy = tap.main:main', - 'tap = tap.main:main', - ], + "console_scripts": ["tappy = tap.main:main", "tap = tap.main:main"] }, include_package_data=True, zip_safe=False, - platforms='any', + platforms="any", install_requires=[], - extras_require={ - 'yaml': [ - 'more-itertools', - 'PyYAML', - ], - }, + extras_require={"yaml": ["more-itertools", "PyYAML"]}, classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: Implementation :: PyPy', - 'Topic :: Software Development :: Testing', - ], - keywords=[ - 'TAP', - 'unittest', + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Software Development :: Testing", ], - cmdclass={ - 'build_py': BuildPy, - 'sdist': Sdist, - }, - test_suite='tap.tests', - tests_require=[ - 'mock' - ] + keywords=["TAP", "unittest"], + cmdclass={"build_py": BuildPy, "sdist": Sdist}, + test_suite="tap.tests", + tests_require=["mock"], ) diff --git a/tap/__init__.py b/tap/__init__.py index a7ef2ad..28fb65f 100644 --- a/tap/__init__.py +++ b/tap/__init__.py @@ -2,5 +2,5 @@ from .runner import TAPTestRunner -__all__ = ['TAPTestRunner'] -__version__ = '2.4' +__all__ = ["TAPTestRunner"] +__version__ = "2.4" diff --git a/tap/adapter.py b/tap/adapter.py index 7ea5532..3311667 100644 --- a/tap/adapter.py +++ b/tap/adapter.py @@ -6,6 +6,7 @@ class Adapter(object): It is an alternative to TestCase to collect TAP results. """ + failureException = AssertionError def __init__(self, filename, line): @@ -45,7 +46,7 @@ def addFailure(self, result): # Since TAP will not provide assertion data, clean up the assertion # section so it is not so spaced out. test, err = result.failures[-1] - result.failures[-1] = (test, '') + result.failures[-1] = (test, "") def __repr__(self): - return ''.format(filename=self._filename) + return "".format(filename=self._filename) diff --git a/tap/directive.py b/tap/directive.py index 50e7303..df6ab87 100644 --- a/tap/directive.py +++ b/tap/directive.py @@ -10,12 +10,14 @@ class Directive(object): r"""^SKIP\S* (?P\s*) # Optional whitespace. (?P.*) # Slurp up the rest.""", - re.IGNORECASE | re.VERBOSE) + re.IGNORECASE | re.VERBOSE, + ) todo_pattern = re.compile( r"""^TODO\b # The directive name (?P\s*) # Immediately following must be whitespace. (?P.*) # Slurp up the rest.""", - re.IGNORECASE | re.VERBOSE) + re.IGNORECASE | re.VERBOSE, + ) def __init__(self, text): """Initialize the directive by parsing the text. @@ -30,17 +32,17 @@ def __init__(self, text): match = self.skip_pattern.match(text) if match: self._skip = True - self._reason = match.group('reason') + self._reason = match.group("reason") match = self.todo_pattern.match(text) if match: - if match.group('whitespace'): + if match.group("whitespace"): self._todo = True else: # Catch the case where the directive has no descriptive text. - if match.group('reason') == '': + if match.group("reason") == "": self._todo = True - self._reason = match.group('reason') + self._reason = match.group("reason") @property def text(self): diff --git a/tap/formatter.py b/tap/formatter.py index f42b150..71e4d59 100644 --- a/tap/formatter.py +++ b/tap/formatter.py @@ -10,7 +10,7 @@ def format_exception(exception): # The lines returned from format_exception do not strictly contain # one line per element in the list (i.e. some elements have new # line characters in the middle). Normalize that oddity. - lines = ''.join(exception_lines).splitlines(True) + lines = "".join(exception_lines).splitlines(True) return format_as_diagnostics(lines) @@ -19,4 +19,4 @@ def format_as_diagnostics(lines): This function makes no assumptions about the line endings. """ - return ''.join(['# ' + line for line in lines]) + return "".join(["# " + line for line in lines]) diff --git a/tap/i18n.py b/tap/i18n.py index bff8366..a3798db 100644 --- a/tap/i18n.py +++ b/tap/i18n.py @@ -3,6 +3,6 @@ import gettext import os -localedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'locale') -translate = gettext.translation('tappy', localedir, fallback=True) +localedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), "locale") +translate = gettext.translation("tappy", localedir, fallback=True) _ = translate.gettext diff --git a/tap/line.py b/tap/line.py index 352bc1b..8ae1c5e 100644 --- a/tap/line.py +++ b/tap/line.py @@ -1,6 +1,7 @@ # Copyright (c) 2018, Matt Layman and contributors try: import yaml + LOAD_YAML = True except ImportError: # pragma: no cover LOAD_YAML = False @@ -11,6 +12,7 @@ class Line(object): TAP is a line based protocol. Thus, the most primitive type is a line. """ + @property def category(self): raise NotImplementedError @@ -20,8 +22,14 @@ class Result(Line): """Information about an individual test line.""" def __init__( - self, ok, number=None, description='', directive=None, - diagnostics=None, raw_yaml_block=None): + self, + ok, + number=None, + description="", + directive=None, + diagnostics=None, + raw_yaml_block=None, + ): self._ok = ok if number: self._number = int(number) @@ -36,7 +44,7 @@ def __init__( @property def category(self): """:returns: ``test``""" - return 'test' + return "test" @property def ok(self): @@ -91,21 +99,22 @@ def yaml_block(self): yaml_dict = yaml.load(self._yaml_block) return yaml_dict except yaml.error.YAMLError: - print('Error parsing yaml block. Check formatting.') + print("Error parsing yaml block. Check formatting.") return None def __str__(self): - is_not = '' + is_not = "" if not self.ok: - is_not = 'not ' - directive = '' + is_not = "not " + directive = "" if self.directive is not None and self.directive.text: - directive = ' # {0}'.format(self.directive.text) - diagnostics = '' + directive = " # {0}".format(self.directive.text) + diagnostics = "" if self.diagnostics is not None: - diagnostics = '\n' + self.diagnostics.rstrip() + diagnostics = "\n" + self.diagnostics.rstrip() return "{0}ok {1} {2}{3}{4}".format( - is_not, self.number, self.description, directive, diagnostics) + is_not, self.number, self.description, directive, diagnostics + ) class Plan(Line): @@ -118,7 +127,7 @@ def __init__(self, expected_tests, directive=None): @property def category(self): """:returns: ``plan``""" - return 'plan' + return "plan" @property def expected_tests(self): @@ -146,7 +155,7 @@ def __init__(self, text): @property def category(self): """:returns: ``diagnostic``""" - return 'diagnostic' + return "diagnostic" @property def text(self): @@ -163,7 +172,7 @@ def __init__(self, reason): @property def category(self): """:returns: ``bail``""" - return 'bail' + return "bail" @property def reason(self): @@ -180,7 +189,7 @@ def __init__(self, version): @property def category(self): """:returns: ``version``""" - return 'version' + return "version" @property def version(self): @@ -196,7 +205,8 @@ class Unknown(Line): This exists for the purpose of a Null Object pattern. """ + @property def category(self): """:returns: ``unknown``""" - return 'unknown' + return "unknown" diff --git a/tap/loader.py b/tap/loader.py index 83cdd50..10d0ab5 100644 --- a/tap/loader.py +++ b/tap/loader.py @@ -11,7 +11,7 @@ class Loader(object): """Load TAP lines into unittest-able objects.""" - ignored_lines = set(['diagnostic', 'unknown']) + ignored_lines = set(["diagnostic", "unknown"]) def __init__(self): self._parser = Parser() @@ -52,9 +52,9 @@ def load_suite_from_stdin(self): :returns: A ``unittest.TestSuite`` instance """ suite = unittest.TestSuite() - rules = Rules('stream', suite) + rules = Rules("stream", suite) line_generator = self._parser.parse_stdin() - return self._load_lines('stream', line_generator, suite, rules) + return self._load_lines("stream", line_generator, suite, rules) def _find_tests_in_directory(self, directory, suite): """Find test files in the directory and add them to the suite.""" @@ -72,18 +72,18 @@ def _load_lines(self, filename, line_generator, suite, rules): if line.category in self.ignored_lines: continue - if line.category == 'test': + if line.category == "test": suite.addTest(Adapter(filename, line)) rules.saw_test() - elif line.category == 'plan': + elif line.category == "plan": if line.skip: rules.handle_skipping_plan(line) return suite rules.saw_plan(line, line_counter) - elif line.category == 'bail': + elif line.category == "bail": rules.handle_bail(line) return suite - elif line.category == 'version': + elif line.category == "version": rules.saw_version_at(line_counter) rules.check(line_counter) diff --git a/tap/main.py b/tap/main.py index fb69712..85d049f 100644 --- a/tap/main.py +++ b/tap/main.py @@ -21,7 +21,7 @@ def main(argv=sys.argv, stream=sys.stderr): def build_suite(args): """Build a test suite by loading TAP files or a TAP stream.""" loader = Loader() - if len(args.files) == 0 or args.files[0] == '-': + if len(args.files) == 0 or args.files[0] == "-": suite = loader.load_suite_from_stdin() else: suite = loader.load(args.files) @@ -29,18 +29,29 @@ def build_suite(args): def parse_args(argv): - description = _('A TAP consumer for Python') + description = _("A TAP consumer for Python") epilog = _( - 'When no files are given or a dash (-) is used for the file name, ' - 'tappy will read a TAP stream from STDIN.') + "When no files are given or a dash (-) is used for the file name, " + "tappy will read a TAP stream from STDIN." + ) parser = argparse.ArgumentParser(description=description, epilog=epilog) parser.add_argument( - 'files', metavar='FILE', nargs='*', help=_( - 'A file containing TAP output. Any directories listed will be ' - 'scanned for files to include as TAP files.')) + "files", + metavar="FILE", + nargs="*", + help=_( + "A file containing TAP output. Any directories listed will be " + "scanned for files to include as TAP files." + ), + ) parser.add_argument( - '-v', '--verbose', action='store_const', default=1, const=2, - help=_('use verbose messages')) + "-v", + "--verbose", + action="store_const", + default=1, + const=2, + help=_("use verbose messages"), + ) # argparse expects the executable to be removed from argv. args = parser.parse_args(argv[1:]) diff --git a/tap/parser.py b/tap/parser.py index 16b3e3c..e79575c 100644 --- a/tap/parser.py +++ b/tap/parser.py @@ -12,6 +12,7 @@ try: from more_itertools import peekable import yaml # noqa + ENABLE_VERSION_13 = True except ImportError: # pragma: no cover ENABLE_VERSION_13 = False @@ -30,26 +31,32 @@ class Parser(object): \s* # Optional whitespace. (?P.*) # Optional directive text. """ - ok = re.compile(r'^ok' + result_base, re.VERBOSE) - not_ok = re.compile(r'^not\ ok' + result_base, re.VERBOSE) - plan = re.compile(r""" + ok = re.compile(r"^ok" + result_base, re.VERBOSE) + not_ok = re.compile(r"^not\ ok" + result_base, re.VERBOSE) + plan = re.compile( + r""" ^1..(?P\d+) # Match the plan details. [^#]* # Consume any non-hash character to confirm only # directives appear with the plan details. \#? # Optional directive marker. \s* # Optional whitespace. (?P.*) # Optional directive text. - """, re.VERBOSE) - diagnostic = re.compile(r'^#') - bail = re.compile(r""" + """, + re.VERBOSE, + ) + diagnostic = re.compile(r"^#") + bail = re.compile( + r""" ^Bail\ out! \s* # Optional whitespace. (?P.*) # Optional reason. - """, re.VERBOSE) - version = re.compile(r'^TAP version (?P\d+)$') + """, + re.VERBOSE, + ) + version = re.compile(r"^TAP version (?P\d+)$") - yaml_block_start = re.compile(r'^(?P\s+)-') - yaml_block_end = re.compile(r'^\s+\.\.\.') + yaml_block_start = re.compile(r"^(?P\s+)-") + yaml_block_end = re.compile(r"^\s+\.\.\.") TAP_MINIMUM_DECLARED_VERSION = 13 @@ -62,7 +69,7 @@ def parse_file(self, filename): This is a generator method that will yield an object for each parsed line. The file given by `filename` is assumed to exist. """ - return self.parse(open(filename, 'r')) + return self.parse(open(filename, "r")) def parse_stdin(self): """Parse a TAP stream from standard input. @@ -93,16 +100,17 @@ def parse(self, fh): return first_parsed = self.parse_line(first_line.rstrip()) fh_new = itertools.chain([first_line], fh) - if first_parsed.category == 'version' and \ - first_parsed.version >= 13: + if first_parsed.category == "version" and first_parsed.version >= 13: if ENABLE_VERSION_13: fh_new = peekable(itertools.chain([first_line], fh)) self._try_peeking = True else: # pragma no cover - print(""" + print( + """ WARNING: Optional imports not found, TAP 13 output will be ignored. To parse yaml, see requirements in docs: - https://tappy.readthedocs.io/en/latest/consumers.html#tap-version-13""") + https://tappy.readthedocs.io/en/latest/consumers.html#tap-version-13""" + ) for line in fh_new: yield self.parse_line(line.rstrip(), fh_new) @@ -126,7 +134,7 @@ def parse_line(self, text, fh=None): match = self.bail.match(text) if match: - return Bail(match.group('reason')) + return Bail(match.group("reason")) match = self.version.match(text) if match: @@ -136,8 +144,8 @@ def parse_line(self, text, fh=None): def _parse_plan(self, match): """Parse a matching plan line.""" - expected_tests = int(match.group('expected')) - directive = Directive(match.group('directive')) + expected_tests = int(match.group("expected")) + directive = Directive(match.group("directive")) # Only SKIP directives are allowed in the plan. if directive.text and not directive.skip: @@ -156,39 +164,40 @@ def _parse_result(self, ok, match, fh=None): if peek_match is None: return Result( ok, - number=match.group('number'), - description=match.group('description').strip(), - directive=Directive(match.group('directive')) + number=match.group("number"), + description=match.group("description").strip(), + directive=Directive(match.group("directive")), ) - indent = peek_match.group('indent') + indent = peek_match.group("indent") concat_yaml = self._extract_yaml_block(indent, fh) return Result( ok, - number=match.group('number'), - description=match.group('description').strip(), - directive=Directive(match.group('directive')), - raw_yaml_block=concat_yaml + number=match.group("number"), + description=match.group("description").strip(), + directive=Directive(match.group("directive")), + raw_yaml_block=concat_yaml, ) def _extract_yaml_block(self, indent, fh): """Extract a raw yaml block from a file handler""" raw_yaml = [] - indent_match = re.compile(r'^{}'.format(indent)) + indent_match = re.compile(r"^{}".format(indent)) try: fh.next() while indent_match.match(fh.peek()): - raw_yaml.append(fh.next().replace(indent, '', 1)) + raw_yaml.append(fh.next().replace(indent, "", 1)) # check for the end and stop adding yaml if encountered if self.yaml_block_end.match(fh.peek()): fh.next() break except StopIteration: pass - return '\n'.join(raw_yaml) + return "\n".join(raw_yaml) def _parse_version(self, match): - version = int(match.group('version')) + version = int(match.group("version")) if version < self.TAP_MINIMUM_DECLARED_VERSION: - raise ValueError(_('It is an error to explicitly specify ' - 'any version lower than 13.')) + raise ValueError( + _("It is an error to explicitly specify any version lower than 13.") + ) return Version(version) diff --git a/tap/rules.py b/tap/rules.py index 23a16f0..c00af19 100644 --- a/tap/rules.py +++ b/tap/rules.py @@ -7,47 +7,48 @@ class Rules(object): - def __init__(self, filename, suite): self._filename = filename self._suite = suite - self._lines_seen = {'plan': [], 'test': 0, 'version': []} + self._lines_seen = {"plan": [], "test": 0, "version": []} def check(self, final_line_count): """Check the status of all provided data and update the suite.""" - if self._lines_seen['version']: + if self._lines_seen["version"]: self._process_version_lines() self._process_plan_lines(final_line_count) def _process_version_lines(self): """Process version line rules.""" - if len(self._lines_seen['version']) > 1: - self._add_error(_('Multiple version lines appeared.')) - elif self._lines_seen['version'][0] != 1: - self._add_error(_('The version must be on the first line.')) + if len(self._lines_seen["version"]) > 1: + self._add_error(_("Multiple version lines appeared.")) + elif self._lines_seen["version"][0] != 1: + self._add_error(_("The version must be on the first line.")) def _process_plan_lines(self, final_line_count): """Process plan line rules.""" - if not self._lines_seen['plan']: - self._add_error(_('Missing a plan.')) + if not self._lines_seen["plan"]: + self._add_error(_("Missing a plan.")) return - if len(self._lines_seen['plan']) > 1: - self._add_error(_('Only one plan line is permitted per file.')) + if len(self._lines_seen["plan"]) > 1: + self._add_error(_("Only one plan line is permitted per file.")) return - plan, at_line = self._lines_seen['plan'][0] + plan, at_line = self._lines_seen["plan"][0] if not self._plan_on_valid_line(at_line, final_line_count): self._add_error( - _('A plan must appear at the beginning or end of the file.')) + _("A plan must appear at the beginning or end of the file.") + ) return - if plan.expected_tests != self._lines_seen['test']: - self._add_error(_( - 'Expected {expected_count} tests ' - 'but only {seen_count} ran.').format( + if plan.expected_tests != self._lines_seen["test"]: + self._add_error( + _("Expected {expected_count} tests but only {seen_count} ran.").format( expected_count=plan.expected_tests, - seen_count=self._lines_seen['test'])) + seen_count=self._lines_seen["test"], + ) + ) def _plan_on_valid_line(self, at_line, final_line_count): """Check if a plan is on a valid line.""" @@ -57,9 +58,10 @@ def _plan_on_valid_line(self, at_line, final_line_count): # The plan may only appear on line 2 if the version is at line 1. after_version = ( - self._lines_seen['version'] and - self._lines_seen['version'][0] == 1 and - at_line == 2) + self._lines_seen["version"] + and self._lines_seen["version"][0] == 1 + and at_line == 2 + ) if after_version: return True @@ -67,32 +69,30 @@ def _plan_on_valid_line(self, at_line, final_line_count): def handle_bail(self, bail): """Handle a bail line.""" - self._add_error(_('Bailed: {reason}').format(reason=bail.reason)) + self._add_error(_("Bailed: {reason}").format(reason=bail.reason)) def handle_file_does_not_exist(self): """Handle a test file that does not exist.""" - self._add_error(_('{filename} does not exist.').format( - filename=self._filename)) + self._add_error(_("{filename} does not exist.").format(filename=self._filename)) def handle_skipping_plan(self, skip_plan): """Handle a plan that contains a SKIP directive.""" - skip_line = Result( - True, None, skip_plan.directive.text, Directive('SKIP')) + skip_line = Result(True, None, skip_plan.directive.text, Directive("SKIP")) self._suite.addTest(Adapter(self._filename, skip_line)) def saw_plan(self, plan, at_line): """Record when a plan line was seen.""" - self._lines_seen['plan'].append((plan, at_line)) + self._lines_seen["plan"].append((plan, at_line)) def saw_test(self): """Record when a test line was seen.""" - self._lines_seen['test'] += 1 + self._lines_seen["test"] += 1 def saw_version_at(self, line_counter): """Record when a version line was seen.""" - self._lines_seen['version'].append(line_counter) + self._lines_seen["version"].append(line_counter) def _add_error(self, message): """Add an error test to the suite.""" - error_line = Result(False, None, message, Directive('')) + error_line = Result(False, None, message, Directive("")) self._suite.addTest(Adapter(self._filename, error_line)) diff --git a/tap/runner.py b/tap/runner.py index eb077c2..7bc2e02 100644 --- a/tap/runner.py +++ b/tap/runner.py @@ -26,15 +26,15 @@ def addError(self, test, err): super(TAPTestResult, self).addError(test, err) diagnostics = formatter.format_exception(err) self.tracker.add_not_ok( - self._cls_name(test), self._description(test), - diagnostics=diagnostics) + self._cls_name(test), self._description(test), diagnostics=diagnostics + ) def addFailure(self, test, err): super(TAPTestResult, self).addFailure(test, err) diagnostics = formatter.format_exception(err) self.tracker.add_not_ok( - self._cls_name(test), self._description(test), - diagnostics=diagnostics) + self._cls_name(test), self._description(test), diagnostics=diagnostics + ) def addSuccess(self, test): super(TAPTestResult, self).addSuccess(test) @@ -42,20 +42,25 @@ def addSuccess(self, test): def addSkip(self, test, reason): super(TAPTestResult, self).addSkip(test, reason) - self.tracker.add_skip( - self._cls_name(test), self._description(test), reason) + self.tracker.add_skip(self._cls_name(test), self._description(test), reason) def addExpectedFailure(self, test, err): super(TAPTestResult, self).addExpectedFailure(test, err) diagnostics = formatter.format_exception(err) self.tracker.add_not_ok( - self._cls_name(test), self._description(test), - 'TODO {}'.format(_('(expected failure)')), diagnostics=diagnostics) + self._cls_name(test), + self._description(test), + "TODO {}".format(_("(expected failure)")), + diagnostics=diagnostics, + ) def addUnexpectedSuccess(self, test): super(TAPTestResult, self).addUnexpectedSuccess(test) - self.tracker.add_ok(self._cls_name(test), self._description(test), - 'TODO {}'.format(_('(unexpected success)'))) + self.tracker.add_ok( + self._cls_name(test), + self._description(test), + "TODO {}".format(_("(unexpected success)")), + ) def _cls_name(self, test): return test.__class__.__name__ @@ -65,12 +70,16 @@ def _description(self, test): try: return self.FORMAT.format( method_name=str(test), - short_description=test.shortDescription() or '') + short_description=test.shortDescription() or "", + ) except KeyError: - sys.exit(_( - 'Bad format string: {format}\n' - 'Replacement options are: {{short_description}} and ' - '{{method_name}}').format(format=self.FORMAT)) + sys.exit( + _( + "Bad format string: {format}\n" + "Replacement options are: {{short_description}} and " + "{{method_name}}" + ).format(format=self.FORMAT) + ) return test.shortDescription() or str(test) @@ -96,13 +105,12 @@ def set_stream(self, streaming): The test runner default output will be suppressed in favor of TAP. """ - self.stream = _WritelnDecorator(open(os.devnull, 'w')) + self.stream = _WritelnDecorator(open(os.devnull, "w")) _tracker.streaming = streaming _tracker.stream = sys.stdout def _makeResult(self): - result = self.resultclass( - self.stream, self.descriptions, self.verbosity) + result = self.resultclass(self.stream, self.descriptions, self.verbosity) result.tracker = _tracker return result diff --git a/tap/tests/factory.py b/tap/tests/factory.py index 15a860f..c550549 100644 --- a/tap/tests/factory.py +++ b/tap/tests/factory.py @@ -11,22 +11,20 @@ class Factory(object): """A factory to produce commonly needed objects""" - def make_ok(self, directive_text=''): - return Result( - True, 1, 'This is a description.', Directive(directive_text)) + def make_ok(self, directive_text=""): + return Result(True, 1, "This is a description.", Directive(directive_text)) - def make_not_ok(self, directive_text=''): - return Result( - False, 1, 'This is a description.', Directive(directive_text)) + def make_not_ok(self, directive_text=""): + return Result(False, 1, "This is a description.", Directive(directive_text)) - def make_bail(self, reason='Because it is busted.'): + def make_bail(self, reason="Because it is busted."): return Bail(reason) - def make_plan(self, expected_tests=99, directive_text=''): + def make_plan(self, expected_tests=99, directive_text=""): return Plan(expected_tests, Directive(directive_text)) def make_test_result(self): - stream = tempfile.TemporaryFile(mode='w') + stream = tempfile.TemporaryFile(mode="w") return TextTestResult(stream, None, 1) def make_exc(self): @@ -35,6 +33,6 @@ def make_exc(self): Doing this intentionally is not straight forward. """ try: - raise ValueError('boom') + raise ValueError("boom") except ValueError: return sys.exc_info() diff --git a/tap/tests/run.py b/tap/tests/run.py index d3991c0..ec6993f 100644 --- a/tap/tests/run.py +++ b/tap/tests/run.py @@ -5,11 +5,11 @@ from tap import TAPTestRunner -if __name__ == '__main__': +if __name__ == "__main__": tests_dir = os.path.dirname(os.path.abspath(__file__)) loader = unittest.TestLoader() tests = loader.discover(tests_dir) runner = TAPTestRunner() - runner.set_outdir('testout') - runner.set_format('Hi: {method_name} - {short_description}') + runner.set_outdir("testout") + runner.set_format("Hi: {method_name} - {short_description}") runner.run(tests) diff --git a/tap/tests/test_adapter.py b/tap/tests/test_adapter.py index c3f16b1..916b459 100644 --- a/tap/tests/test_adapter.py +++ b/tap/tests/test_adapter.py @@ -14,7 +14,7 @@ class TestAdapter(TestCase): def test_adapter_has_filename(self): """The adapter has a TAP filename.""" - tap_filename = 'fake.tap' + tap_filename = "fake.tap" adapter = Adapter(tap_filename, None) self.assertEqual(tap_filename, adapter._filename) @@ -22,7 +22,7 @@ def test_adapter_has_filename(self): def test_handles_ok_test_line(self): """Add a success for an ok test line.""" ok_line = self.factory.make_ok() - adapter = Adapter('fake.tap', ok_line) + adapter = Adapter("fake.tap", ok_line) result = mock.Mock() adapter(result) @@ -31,21 +31,19 @@ def test_handles_ok_test_line(self): def test_handles_skip_test_line(self): """Add a skip when a test line contains a skip directive.""" - skip_line = self.factory.make_ok( - directive_text='SKIP This is the reason.') - adapter = Adapter('fake.tap', skip_line) + skip_line = self.factory.make_ok(directive_text="SKIP This is the reason.") + adapter = Adapter("fake.tap", skip_line) result = self.factory.make_test_result() adapter(result) self.assertEqual(1, len(result.skipped)) - self.assertEqual('This is the reason.', result.skipped[0][1]) + self.assertEqual("This is the reason.", result.skipped[0][1]) def test_handles_ok_todo_test_line(self): """Add an unexpected success for an ok todo test line.""" - todo_line = self.factory.make_ok( - directive_text='TODO An incomplete test') - adapter = Adapter('fake.tap', todo_line) + todo_line = self.factory.make_ok(directive_text="TODO An incomplete test") + adapter = Adapter("fake.tap", todo_line) result = self.factory.make_test_result() adapter(result) @@ -54,9 +52,8 @@ def test_handles_ok_todo_test_line(self): def test_handles_not_ok_todo_test_line(self): """Add an expected failure for a not ok todo test line.""" - todo_line = self.factory.make_not_ok( - directive_text='TODO An incomplete test') - adapter = Adapter('fake.tap', todo_line) + todo_line = self.factory.make_not_ok(directive_text="TODO An incomplete test") + adapter = Adapter("fake.tap", todo_line) result = self.factory.make_test_result() adapter(result) diff --git a/tap/tests/test_directive.py b/tap/tests/test_directive.py index 6ee273e..8109a29 100644 --- a/tap/tests/test_directive.py +++ b/tap/tests/test_directive.py @@ -9,39 +9,39 @@ class TestDirective(unittest.TestCase): """Tests for tap.directive.Directive""" def test_finds_todo(self): - text = 'ToDo This is something to do.' + text = "ToDo This is something to do." directive = Directive(text) self.assertTrue(directive.todo) def test_finds_simplest_todo(self): - text = 'TODO' + text = "TODO" directive = Directive(text) self.assertTrue(directive.todo) def test_todo_has_boundary(self): """TAP spec indicates TODO directives must be on a boundary.""" - text = 'TODO: Not a TODO directive because of an immediate colon.' + text = "TODO: Not a TODO directive because of an immediate colon." directive = Directive(text) self.assertFalse(directive.todo) def test_finds_skip(self): - text = 'Skipping This is something to skip.' + text = "Skipping This is something to skip." directive = Directive(text) self.assertTrue(directive.skip) def test_finds_simplest_skip(self): - text = 'SKIP' + text = "SKIP" directive = Directive(text) self.assertTrue(directive.skip) def test_skip_at_beginning(self): """Only match SKIP directives at the beginning.""" - text = 'This is not something to skip.' + text = "This is not something to skip." directive = Directive(text) self.assertFalse(directive.skip) diff --git a/tap/tests/test_formatter.py b/tap/tests/test_formatter.py index ad274b2..ec0e20e 100644 --- a/tap/tests/test_formatter.py +++ b/tap/tests/test_formatter.py @@ -3,15 +3,14 @@ class TestFormatter(TestCase): - def test_formats_as_diagnostics(self): - data = ['foo\n', 'bar\n'] - expected_diagnostics = '# foo\n# bar\n' + data = ["foo\n", "bar\n"] + expected_diagnostics = "# foo\n# bar\n" diagnostics = format_as_diagnostics(data) self.assertEqual(expected_diagnostics, diagnostics) def test_format_exception_as_diagnostics(self): exc = self.factory.make_exc() diagnostics = format_exception(exc) - self.assertTrue(diagnostics.startswith('# ')) - self.assertTrue('boom' in diagnostics) + self.assertTrue(diagnostics.startswith("# ")) + self.assertTrue("boom" in diagnostics) diff --git a/tap/tests/test_line.py b/tap/tests/test_line.py index abf13fa..4ab15a5 100644 --- a/tap/tests/test_line.py +++ b/tap/tests/test_line.py @@ -20,29 +20,25 @@ class TestResult(unittest.TestCase): def test_category(self): result = Result(True) - self.assertEqual('test', result.category) + self.assertEqual("test", result.category) def test_ok(self): result = Result(True) self.assertTrue(result.ok) def test_str_ok(self): - result = Result(True, 42, 'passing') - self.assertEqual( - 'ok 42 passing', str(result)) + result = Result(True, 42, "passing") + self.assertEqual("ok 42 passing", str(result)) def test_str_not_ok(self): - result = Result(False, 43, 'failing') - self.assertEqual( - 'not ok 43 failing', str(result)) + result = Result(False, 43, "failing") + self.assertEqual("not ok 43 failing", str(result)) def test_str_directive(self): - directive = Directive('SKIP a reason') - result = Result(True, 44, 'passing', directive) - self.assertEqual( - 'ok 44 passing # SKIP a reason', str(result)) + directive = Directive("SKIP a reason") + result = Result(True, 44, "passing", directive) + self.assertEqual("ok 44 passing # SKIP a reason", str(result)) def test_str_diagnostics(self): - result = Result(False, 43, 'failing', diagnostics='# more info') - self.assertEqual( - 'not ok 43 failing\n# more info', str(result)) + result = Result(False, 43, "failing", diagnostics="# more info") + self.assertEqual("not ok 43 failing\n# more info", str(result)) diff --git a/tap/tests/test_loader.py b/tap/tests/test_loader.py index 8dcd5e1..027c380 100644 --- a/tap/tests/test_loader.py +++ b/tap/tests/test_loader.py @@ -29,9 +29,10 @@ def test_handles_file(self): not ok 2 A failing test This is an unknown line. Bail out! This test would abort. - """) + """ + ) temp = tempfile.NamedTemporaryFile(delete=False) - temp.write(sample.encode('utf-8')) + temp.write(sample.encode("utf-8")) temp.close() loader = Loader() @@ -44,21 +45,22 @@ def test_file_does_not_exist(self): """The loader records a failure when a file does not exist.""" loader = Loader() - suite = loader.load_suite_from_file('phony.tap') + suite = loader.load_suite_from_file("phony.tap") self.assertEqual(1, len(suite._tests)) self.assertEqual( - _('{filename} does not exist.').format(filename='phony.tap'), - suite._tests[0]._line.description) + _("{filename} does not exist.").format(filename="phony.tap"), + suite._tests[0]._line.description, + ) def test_handles_directory(self): directory = tempfile.mkdtemp() - sub_directory = os.path.join(directory, 'sub') + sub_directory = os.path.join(directory, "sub") os.mkdir(sub_directory) - with open(os.path.join(directory, 'a_file.tap'), 'w') as f: - f.write('ok A passing test') - with open(os.path.join(sub_directory, 'another_file.tap'), 'w') as f: - f.write('not ok A failing test') + with open(os.path.join(directory, "a_file.tap"), "w") as f: + f.write("ok A passing test") + with open(os.path.join(sub_directory, "another_file.tap"), "w") as f: + f.write("not ok A failing test") loader = Loader() suite = loader.load([directory]) @@ -70,9 +72,10 @@ def test_errors_with_multiple_version_lines(self): """TAP version 13 TAP version 13 1..0 - """) + """ + ) temp = tempfile.NamedTemporaryFile(delete=False) - temp.write(sample.encode('utf-8')) + temp.write(sample.encode("utf-8")) temp.close() loader = Loader() @@ -80,17 +83,18 @@ def test_errors_with_multiple_version_lines(self): self.assertEqual(1, len(suite._tests)) self.assertEqual( - _('Multiple version lines appeared.'), - suite._tests[0]._line.description) + _("Multiple version lines appeared."), suite._tests[0]._line.description + ) def test_errors_with_version_not_on_first_line(self): sample = inspect.cleandoc( """# Something that doesn't belong. TAP version 13 1..0 - """) + """ + ) temp = tempfile.NamedTemporaryFile(delete=False) - temp.write(sample.encode('utf-8')) + temp.write(sample.encode("utf-8")) temp.close() loader = Loader() @@ -98,26 +102,27 @@ def test_errors_with_version_not_on_first_line(self): self.assertEqual(1, len(suite._tests)) self.assertEqual( - _('The version must be on the first line.'), - suite._tests[0]._line.description) + _("The version must be on the first line."), + suite._tests[0]._line.description, + ) def test_skip_plan_aborts_loading(self): sample = inspect.cleandoc( """1..0 # Skipping this test file. ok This should not get processed. - """) + """ + ) temp = tempfile.NamedTemporaryFile(delete=False) - temp.write(sample.encode('utf-8')) + temp.write(sample.encode("utf-8")) temp.close() loader = Loader() suite = loader.load_suite_from_file(temp.name) self.assertEqual(1, len(suite._tests)) - self.assertEqual( - 'Skipping this test file.', suite._tests[0]._line.description) + self.assertEqual("Skipping this test file.", suite._tests[0]._line.description) - @mock.patch('tap.parser.sys.stdin', StringIO(u'')) + @mock.patch("tap.parser.sys.stdin", StringIO(u"")) def test_loads_from_stream(self): loader = Loader() suite = loader.load_suite_from_stdin() diff --git a/tap/tests/test_main.py b/tap/tests/test_main.py index 9eab0f6..b39e8c8 100644 --- a/tap/tests/test_main.py +++ b/tap/tests/test_main.py @@ -18,8 +18,8 @@ class TestMain(TestCase): def test_exits_with_error(self): """The main function returns an error status if there were failures.""" - argv = ['/bin/fake', 'fake.tap'] - stream = open(os.devnull, 'w') + argv = ["/bin/fake", "fake.tap"] + stream = open(os.devnull, "w") status = main(argv, stream=stream) @@ -30,7 +30,7 @@ def test_get_successful_status(self): result.wasSuccessful.return_value = True self.assertEqual(0, get_status(result)) - @mock.patch.object(Loader, 'load_suite_from_stdin') + @mock.patch.object(Loader, "load_suite_from_stdin") def test_build_suite_from_stdin(self, load_suite_from_stdin): args = mock.Mock() args.files = [] @@ -39,20 +39,20 @@ def test_build_suite_from_stdin(self, load_suite_from_stdin): suite = build_suite(args) self.assertEqual(expected_suite, suite) - @mock.patch.object(Loader, 'load_suite_from_stdin') + @mock.patch.object(Loader, "load_suite_from_stdin") def test_build_suite_from_stdin_dash(self, load_suite_from_stdin): - argv = ['/bin/fake', '-'] + argv = ["/bin/fake", "-"] args = parse_args(argv) expected_suite = mock.Mock() load_suite_from_stdin.return_value = expected_suite suite = build_suite(args) self.assertEqual(expected_suite, suite) - @mock.patch('tap.main.sys.stdin') - @mock.patch('tap.main.sys.exit') - @mock.patch.object(argparse.ArgumentParser, 'print_help') + @mock.patch("tap.main.sys.stdin") + @mock.patch("tap.main.sys.exit") + @mock.patch.object(argparse.ArgumentParser, "print_help") def test_when_no_pipe_to_stdin(self, print_help, sys_exit, mock_stdin): - argv = ['/bin/fake'] + argv = ["/bin/fake"] mock_stdin.isatty = mock.Mock(return_value=True) parse_args(argv) self.assertTrue(print_help.called) diff --git a/tap/tests/test_parser.py b/tap/tests/test_parser.py index 4e919d1..5eef987 100644 --- a/tap/tests/test_parser.py +++ b/tap/tests/test_parser.py @@ -36,9 +36,9 @@ def test_finds_ok(self): """The parser extracts an ok line.""" parser = Parser() - line = parser.parse_line('ok - This is a passing test line.') + line = parser.parse_line("ok - This is a passing test line.") - self.assertEqual('test', line.category) + self.assertEqual("test", line.category) self.assertTrue(line.ok) self.assertTrue(line.number is None) @@ -46,102 +46,102 @@ def test_finds_number(self): """The parser extracts a test number.""" parser = Parser() - line = parser.parse_line('ok 42 is the magic number.') + line = parser.parse_line("ok 42 is the magic number.") - self.assertEqual('test', line.category) + self.assertEqual("test", line.category) self.assertEqual(42, line.number) def test_finds_description(self): parser = Parser() - line = parser.parse_line('ok 42 A passing test.') + line = parser.parse_line("ok 42 A passing test.") - self.assertEqual('test', line.category) - self.assertEqual('A passing test.', line.description) + self.assertEqual("test", line.category) + self.assertEqual("A passing test.", line.description) def test_after_hash_is_not_description(self): parser = Parser() - line = parser.parse_line('ok A description # Not part of description.') + line = parser.parse_line("ok A description # Not part of description.") - self.assertEqual('test', line.category) - self.assertEqual('A description', line.description) + self.assertEqual("test", line.category) + self.assertEqual("A description", line.description) def test_finds_todo(self): parser = Parser() - line = parser.parse_line('ok A description # TODO Not done') + line = parser.parse_line("ok A description # TODO Not done") - self.assertEqual('test', line.category) + self.assertEqual("test", line.category) self.assertTrue(line.todo) def test_finds_skip(self): parser = Parser() - line = parser.parse_line('ok A description # SKIP for now') + line = parser.parse_line("ok A description # SKIP for now") - self.assertEqual('test', line.category) + self.assertEqual("test", line.category) self.assertTrue(line.skip) def test_finds_not_ok(self): """The parser extracts a not ok line.""" parser = Parser() - line = parser.parse_line('not ok - This is a failing test line.') + line = parser.parse_line("not ok - This is a failing test line.") - self.assertEqual('test', line.category) + self.assertEqual("test", line.category) self.assertFalse(line.ok) self.assertTrue(line.number is None) - self.assertEqual('', line.directive.text) + self.assertEqual("", line.directive.text) def test_finds_directive(self): """The parser extracts a directive""" parser = Parser() - test_line = 'not ok - This line fails # TODO not implemented' + test_line = "not ok - This line fails # TODO not implemented" line = parser.parse_line(test_line) directive = line.directive - self.assertEqual('test', line.category) - self.assertEqual('TODO not implemented', directive.text) + self.assertEqual("test", line.category) + self.assertEqual("TODO not implemented", directive.text) self.assertFalse(directive.skip) self.assertTrue(directive.todo) - self.assertEqual('not implemented', directive.reason) + self.assertEqual("not implemented", directive.reason) def test_unrecognizable_line(self): """The parser returns an unrecognizable line.""" parser = Parser() - line = parser.parse_line('This is not a valid TAP line. # srsly') + line = parser.parse_line("This is not a valid TAP line. # srsly") - self.assertEqual('unknown', line.category) + self.assertEqual("unknown", line.category) def test_diagnostic_line(self): """The parser extracts a diagnostic line.""" - text = '# An example diagnostic line' + text = "# An example diagnostic line" parser = Parser() line = parser.parse_line(text) - self.assertEqual('diagnostic', line.category) + self.assertEqual("diagnostic", line.category) self.assertEqual(text, line.text) def test_bail_out_line(self): """The parser extracts a bail out line.""" parser = Parser() - line = parser.parse_line('Bail out! This is the reason to bail.') + line = parser.parse_line("Bail out! This is the reason to bail.") - self.assertEqual('bail', line.category) - self.assertEqual('This is the reason to bail.', line.reason) + self.assertEqual("bail", line.category) + self.assertEqual("This is the reason to bail.", line.reason) def test_finds_version(self): """The parser extracts a version line.""" parser = Parser() - line = parser.parse_line('TAP version 13') + line = parser.parse_line("TAP version 13") - self.assertEqual('version', line.category) + self.assertEqual("version", line.category) self.assertEqual(13, line.version) def test_errors_on_old_version(self): @@ -149,39 +149,40 @@ def test_errors_on_old_version(self): parser = Parser() with self.assertRaises(ValueError): - parser.parse_line('TAP version 12') + parser.parse_line("TAP version 12") def test_finds_plan(self): """The parser extracts a plan line.""" parser = Parser() - line = parser.parse_line('1..42') + line = parser.parse_line("1..42") - self.assertEqual('plan', line.category) + self.assertEqual("plan", line.category) self.assertEqual(42, line.expected_tests) def test_finds_plan_with_skip(self): """The parser extracts a plan line containing a SKIP.""" parser = Parser() - line = parser.parse_line('1..42 # Skipping this test file.') + line = parser.parse_line("1..42 # Skipping this test file.") - self.assertEqual('plan', line.category) + self.assertEqual("plan", line.category) self.assertTrue(line.skip) def test_ignores_plan_with_any_non_skip_directive(self): """The parser only recognizes SKIP directives in plans.""" parser = Parser() - line = parser.parse_line('1..42 # TODO will not work.') + line = parser.parse_line("1..42 # TODO will not work.") - self.assertEqual('unknown', line.category) + self.assertEqual("unknown", line.category) def test_parses_text(self): sample = inspect.cleandoc( u"""1..2 ok 1 A passing test - not ok 2 A failing test""") + not ok 2 A failing test""" + ) parser = Parser() lines = [] @@ -189,19 +190,20 @@ def test_parses_text(self): lines.append(line) self.assertEqual(3, len(lines)) - self.assertEqual('plan', lines[0].category) - self.assertEqual('test', lines[1].category) + self.assertEqual("plan", lines[0].category) + self.assertEqual("test", lines[1].category) self.assertTrue(lines[1].ok) - self.assertEqual('test', lines[2].category) + self.assertEqual("test", lines[2].category) self.assertFalse(lines[2].ok) def test_parses_file(self): sample = inspect.cleandoc( """1..2 ok 1 A passing test - not ok 2 A failing test""") + not ok 2 A failing test""" + ) temp = tempfile.NamedTemporaryFile(delete=False) - temp.write(sample.encode('utf-8')) + temp.write(sample.encode("utf-8")) temp.close() parser = Parser() lines = [] @@ -210,11 +212,11 @@ def test_parses_file(self): lines.append(line) self.assertEqual(3, len(lines)) - self.assertEqual('plan', lines[0].category) - self.assertEqual('test', lines[1].category) + self.assertEqual("plan", lines[0].category) + self.assertEqual("test", lines[1].category) self.assertTrue(lines[1].ok) self.assertIsNone(lines[1].yaml_block) - self.assertEqual('test', lines[2].category) + self.assertEqual("test", lines[2].category) self.assertFalse(lines[2].ok) def test_parses_yaml(self): @@ -225,7 +227,8 @@ def test_parses_yaml(self): --- test: sample yaml ... - not ok 2 A failing test""") + not ok 2 A failing test""" + ) parser = Parser() lines = [] @@ -235,18 +238,19 @@ def test_parses_yaml(self): try: import yaml from more_itertools import peekable # noqa + converted_yaml = yaml.load(u"""test: sample yaml""") self.assertEqual(4, len(lines)) self.assertEqual(13, lines[0].version) self.assertEqual(converted_yaml, lines[2].yaml_block) - self.assertEqual('test', lines[3].category) + self.assertEqual("test", lines[3].category) self.assertIsNone(lines[3].yaml_block) except ImportError: self.assertEqual(7, len(lines)) self.assertEqual(13, lines[0].version) for l in list(range(3, 6)): - self.assertEqual('unknown', lines[l].category) - self.assertEqual('test', lines[6].category) + self.assertEqual("unknown", lines[l].category) + self.assertEqual("test", lines[6].category) def test_parses_yaml_no_end(self): sample = inspect.cleandoc( @@ -255,7 +259,8 @@ def test_parses_yaml_no_end(self): ok 1 A passing test --- test: sample yaml - not ok 2 A failing test""") + not ok 2 A failing test""" + ) parser = Parser() lines = [] @@ -265,18 +270,19 @@ def test_parses_yaml_no_end(self): try: import yaml from more_itertools import peekable # noqa + converted_yaml = yaml.load(u"""test: sample yaml""") self.assertEqual(4, len(lines)) self.assertEqual(13, lines[0].version) self.assertEqual(converted_yaml, lines[2].yaml_block) - self.assertEqual('test', lines[3].category) + self.assertEqual("test", lines[3].category) self.assertIsNone(lines[3].yaml_block) except ImportError: self.assertEqual(6, len(lines)) self.assertEqual(13, lines[0].version) for l in list(range(3, 5)): - self.assertEqual('unknown', lines[l].category) - self.assertEqual('test', lines[5].category) + self.assertEqual("unknown", lines[l].category) + self.assertEqual("test", lines[5].category) def test_parses_yaml_more_complex(self): sample = inspect.cleandoc( @@ -290,7 +296,8 @@ def test_parses_yaml_more_complex(self): got: - foo expect: - - bar""") + - bar""" + ) parser = Parser() lines = [] @@ -300,14 +307,17 @@ def test_parses_yaml_more_complex(self): try: import yaml from more_itertools import peekable # noqa - converted_yaml = yaml.load(u""" + + converted_yaml = yaml.load( + u""" message: test severity: fail data: got: - foo expect: - - bar""") + - bar""" + ) self.assertEqual(3, len(lines)) self.assertEqual(13, lines[0].version) self.assertEqual(converted_yaml, lines[2].yaml_block) @@ -315,7 +325,7 @@ def test_parses_yaml_more_complex(self): self.assertEqual(11, len(lines)) self.assertEqual(13, lines[0].version) for l in list(range(3, 11)): - self.assertEqual('unknown', lines[l].category) + self.assertEqual("unknown", lines[l].category) def test_parses_yaml_no_association(self): sample = inspect.cleandoc( @@ -326,7 +336,8 @@ def test_parses_yaml_no_association(self): --- test: sample yaml ... - not ok 2 A failing test""") + not ok 2 A failing test""" + ) parser = Parser() lines = [] @@ -336,10 +347,10 @@ def test_parses_yaml_no_association(self): self.assertEqual(8, len(lines)) self.assertEqual(13, lines[0].version) self.assertIsNone(lines[2].yaml_block) - self.assertEqual('diagnostic', lines[3].category) + self.assertEqual("diagnostic", lines[3].category) for l in list(range(4, 7)): - self.assertEqual('unknown', lines[l].category) - self.assertEqual('test', lines[7].category) + self.assertEqual("unknown", lines[l].category) + self.assertEqual("test", lines[7].category) def test_parses_yaml_no_start(self): sample = inspect.cleandoc( @@ -348,7 +359,8 @@ def test_parses_yaml_no_start(self): ok 1 A passing test test: sample yaml ... - not ok 2 A failing test""") + not ok 2 A failing test""" + ) parser = Parser() lines = [] @@ -359,8 +371,8 @@ def test_parses_yaml_no_start(self): self.assertEqual(13, lines[0].version) self.assertIsNone(lines[2].yaml_block) for l in list(range(3, 5)): - self.assertEqual('unknown', lines[l].category) - self.assertEqual('test', lines[5].category) + self.assertEqual("unknown", lines[l].category) + self.assertEqual("test", lines[5].category) def test_malformed_yaml(self): self.maxDiff = None @@ -372,12 +384,14 @@ def test_malformed_yaml(self): test: sample yaml \tfail: tabs are not allowed! ... - not ok 2 A failing test""") + not ok 2 A failing test""" + ) yaml_err = inspect.cleandoc( u""" WARNING: Optional imports not found, TAP 13 output will be ignored. To parse yaml, see requirements in docs: - https://tappy.readthedocs.io/en/latest/consumers.html#tap-version-13""") + https://tappy.readthedocs.io/en/latest/consumers.html#tap-version-13""" + ) parser = Parser() lines = [] @@ -388,23 +402,23 @@ def test_malformed_yaml(self): try: import yaml # noqa from more_itertools import peekable # noqa + self.assertEqual(4, len(lines)) self.assertEqual(13, lines[0].version) with captured_output() as (out, _): self.assertIsNone(lines[2].yaml_block) self.assertEqual( - 'Error parsing yaml block. Check formatting.', - out.getvalue().strip()) - self.assertEqual('test', lines[3].category) + "Error parsing yaml block. Check formatting.", out.getvalue().strip() + ) + self.assertEqual("test", lines[3].category) self.assertIsNone(lines[3].yaml_block) except ImportError: self.assertEqual(8, len(lines)) self.assertEqual(13, lines[0].version) for l in list(range(3, 7)): - self.assertEqual('unknown', lines[l].category) - self.assertEqual('test', lines[7].category) - self.assertEqual( - yaml_err, parse_out.getvalue().strip()) + self.assertEqual("unknown", lines[l].category) + self.assertEqual("test", lines[7].category) + self.assertEqual(yaml_err, parse_out.getvalue().strip()) def test_parse_empty_file(self): temp = tempfile.NamedTemporaryFile(delete=False) @@ -417,10 +431,14 @@ def test_parse_empty_file(self): self.assertEqual(0, len(lines)) - @mock.patch('tap.parser.sys.stdin', - StringIO(u"""1..2 + @mock.patch( + "tap.parser.sys.stdin", + StringIO( + u"""1..2 ok 1 A passing test -not ok 2 A failing test""")) +not ok 2 A failing test""" + ), + ) def test_parses_stdin(self): parser = Parser() lines = [] @@ -429,8 +447,8 @@ def test_parses_stdin(self): lines.append(line) self.assertEqual(3, len(lines)) - self.assertEqual('plan', lines[0].category) - self.assertEqual('test', lines[1].category) + self.assertEqual("plan", lines[0].category) + self.assertEqual("test", lines[1].category) self.assertTrue(lines[1].ok) - self.assertEqual('test', lines[2].category) + self.assertEqual("test", lines[2].category) self.assertFalse(lines[2].ok) diff --git a/tap/tests/test_result.py b/tap/tests/test_result.py index 93b0a57..06fe56c 100644 --- a/tap/tests/test_result.py +++ b/tap/tests/test_result.py @@ -10,7 +10,6 @@ class FakeTestCase(unittest.TestCase): - def runTest(self): pass @@ -19,11 +18,10 @@ def __call__(self, result): class TestTAPTestResult(TestCase): - @classmethod def _make_one(cls): # Yep, the stream is not being closed. - stream = open(os.devnull, 'w') + stream = open(os.devnull, "w") result = TAPTestResult(stream, False, 0) result.tracker = Tracker() return result @@ -35,7 +33,7 @@ def test_adds_error(self): ex = Exception() ex.__cause__ = None result.addError(FakeTestCase(), (None, ex, None)) - self.assertEqual(len(result.tracker._test_cases['FakeTestCase']), 1) + self.assertEqual(len(result.tracker._test_cases["FakeTestCase"]), 1) def test_adds_failure(self): result = self._make_one() @@ -44,31 +42,31 @@ def test_adds_failure(self): ex = Exception() ex.__cause__ = None result.addFailure(FakeTestCase(), (None, ex, None)) - self.assertEqual(len(result.tracker._test_cases['FakeTestCase']), 1) + self.assertEqual(len(result.tracker._test_cases["FakeTestCase"]), 1) def test_adds_success(self): result = self._make_one() result.addSuccess(FakeTestCase()) - self.assertEqual(len(result.tracker._test_cases['FakeTestCase']), 1) + self.assertEqual(len(result.tracker._test_cases["FakeTestCase"]), 1) def test_adds_skip(self): result = self._make_one() - result.addSkip(FakeTestCase(), 'a reason') - self.assertEqual(len(result.tracker._test_cases['FakeTestCase']), 1) + result.addSkip(FakeTestCase(), "a reason") + self.assertEqual(len(result.tracker._test_cases["FakeTestCase"]), 1) def test_adds_expected_failure(self): exc = self.factory.make_exc() result = self._make_one() result.addExpectedFailure(FakeTestCase(), exc) - line = result.tracker._test_cases['FakeTestCase'][0] + line = result.tracker._test_cases["FakeTestCase"][0] self.assertFalse(line.ok) - self.assertEqual( - line.directive.text, 'TODO {}'.format(_('(expected failure)'))) + self.assertEqual(line.directive.text, "TODO {}".format(_("(expected failure)"))) def test_adds_unexpected_success(self): result = self._make_one() result.addUnexpectedSuccess(FakeTestCase()) - line = result.tracker._test_cases['FakeTestCase'][0] + line = result.tracker._test_cases["FakeTestCase"][0] self.assertTrue(line.ok) self.assertEqual( - line.directive.text, 'TODO {}'.format(_('(unexpected success)'))) + line.directive.text, "TODO {}".format(_("(unexpected success)")) + ) diff --git a/tap/tests/test_rules.py b/tap/tests/test_rules.py index a01f5ea..e90ec1e 100644 --- a/tap/tests/test_rules.py +++ b/tap/tests/test_rules.py @@ -12,17 +12,16 @@ class TestRules(TestCase): def _make_one(self): self.suite = unittest.TestSuite() - return Rules('foobar.tap', self.suite) + return Rules("foobar.tap", self.suite) def test_handles_skipping_plan(self): - skip_plan = self.factory.make_plan(directive_text='Skip on Mondays.') + skip_plan = self.factory.make_plan(directive_text="Skip on Mondays.") rules = self._make_one() rules.handle_skipping_plan(skip_plan) self.assertEqual(1, len(self.suite._tests)) - self.assertEqual( - 'Skip on Mondays.', self.suite._tests[0]._line.description) + self.assertEqual("Skip on Mondays.", self.suite._tests[0]._line.description) def test_tracks_plan_line(self): plan = self.factory.make_plan() @@ -30,8 +29,8 @@ def test_tracks_plan_line(self): rules.saw_plan(plan, 28) - self.assertEqual(rules._lines_seen['plan'][0][0], plan) - self.assertEqual(rules._lines_seen['plan'][0][1], 28) + self.assertEqual(rules._lines_seen["plan"][0][0], plan) + self.assertEqual(rules._lines_seen["plan"][0][1], 28) def test_errors_plan_not_at_end(self): plan = self.factory.make_plan() @@ -41,16 +40,16 @@ def test_errors_plan_not_at_end(self): rules.check(42) self.assertEqual( - _('A plan must appear at the beginning or end of the file.'), - self.suite._tests[0]._line.description) + _("A plan must appear at the beginning or end of the file."), + self.suite._tests[0]._line.description, + ) def test_requires_plan(self): rules = self._make_one() rules.check(42) - self.assertEqual( - _('Missing a plan.'), self.suite._tests[0]._line.description) + self.assertEqual(_("Missing a plan."), self.suite._tests[0]._line.description) def test_only_one_plan(self): plan = self.factory.make_plan() @@ -61,8 +60,9 @@ def test_only_one_plan(self): rules.check(42) self.assertEqual( - _('Only one plan line is permitted per file.'), - self.suite._tests[0]._line.description) + _("Only one plan line is permitted per file."), + self.suite._tests[0]._line.description, + ) def test_plan_line_two(self): """A plan may appear on line 2 when line 1 is a version line.""" @@ -82,18 +82,20 @@ def test_errors_when_expected_tests_differs_from_actual(self): rules.check(2) self.assertEqual( - _('Expected {expected_count} tests but only ' - '{seen_count} ran.').format(expected_count=42, seen_count=1), - self.suite._tests[0]._line.description) + _("Expected {expected_count} tests but only {seen_count} ran.").format( + expected_count=42, seen_count=1 + ), + self.suite._tests[0]._line.description, + ) def test_errors_on_bail(self): - bail = self.factory.make_bail(reason='Missing something important.') + bail = self.factory.make_bail(reason="Missing something important.") rules = self._make_one() rules.handle_bail(bail) self.assertEqual(1, len(self.suite._tests)) self.assertEqual( - _('Bailed: {reason}').format( - reason='Missing something important.'), - self.suite._tests[0]._line.description) + _("Bailed: {reason}").format(reason="Missing something important."), + self.suite._tests[0]._line.description, + ) diff --git a/tap/tests/test_runner.py b/tap/tests/test_runner.py index 7b7c2a2..32eea8c 100644 --- a/tap/tests/test_runner.py +++ b/tap/tests/test_runner.py @@ -15,7 +15,6 @@ class TestTAPTestRunner(unittest.TestCase): - def test_has_tap_test_result(self): runner = TAPTestRunner() self.assertEqual(runner.resultclass, TAPTestResult) @@ -60,7 +59,7 @@ def test_runner_uses_combined(self): _tracker.combined = previous_combined - @mock.patch('sys.exit') + @mock.patch("sys.exit") def test_bad_format_string(self, fake_exit): """A bad format string exits the runner.""" previous_format = TAPTestResult.FORMAT diff --git a/tap/tests/test_tracker.py b/tap/tests/test_tracker.py index 9bc4ae5..0368470 100644 --- a/tap/tests/test_tracker.py +++ b/tap/tests/test_tracker.py @@ -3,6 +3,7 @@ import inspect import os import tempfile + try: from cStringIO import StringIO except ImportError: @@ -19,9 +20,8 @@ class TestTracker(TestCase): - def _make_header(self, test_case): - return _('# TAP results for {test_case}').format(test_case=test_case) + return _("# TAP results for {test_case}").format(test_case=test_case) def test_has_test_cases(self): tracker = Tracker() @@ -29,50 +29,50 @@ def test_has_test_cases(self): def test_tracks_class(self): tracker = Tracker() - tracker._track('FakeTestClass') - self.assertEqual(tracker._test_cases.get('FakeTestClass'), []) + tracker._track("FakeTestClass") + self.assertEqual(tracker._test_cases.get("FakeTestClass"), []) def test_adds_ok(self): tracker = Tracker() - tracker.add_ok('FakeTestCase', 'a description') - line = tracker._test_cases['FakeTestCase'][0] + tracker.add_ok("FakeTestCase", "a description") + line = tracker._test_cases["FakeTestCase"][0] self.assertTrue(line.ok) - self.assertEqual(line.description, 'a description') + self.assertEqual(line.description, "a description") def test_adds_not_ok(self): tracker = Tracker() - tracker.add_not_ok('FakeTestCase', 'a description') - line = tracker._test_cases['FakeTestCase'][0] + tracker.add_not_ok("FakeTestCase", "a description") + line = tracker._test_cases["FakeTestCase"][0] self.assertFalse(line.ok) - self.assertEqual(line.description, 'a description') + self.assertEqual(line.description, "a description") def test_adds_skip(self): tracker = Tracker() - tracker.add_skip('FakeTestCase', 'a description', 'a reason') - line = tracker._test_cases['FakeTestCase'][0] + tracker.add_skip("FakeTestCase", "a description", "a reason") + line = tracker._test_cases["FakeTestCase"][0] self.assertTrue(line.ok) - self.assertEqual(line.description, 'a description') - self.assertEqual(line.directive.text, 'SKIP a reason') + self.assertEqual(line.description, "a description") + self.assertEqual(line.directive.text, "SKIP a reason") def test_generates_tap_reports_in_new_outdir(self): tempdir = tempfile.mkdtemp() - outdir = os.path.join(tempdir, 'non', 'existent', 'path') + outdir = os.path.join(tempdir, "non", "existent", "path") tracker = Tracker(outdir=outdir) - tracker.add_ok('FakeTestCase', 'I should be in the specified dir.') + tracker.add_ok("FakeTestCase", "I should be in the specified dir.") tracker.generate_tap_reports() - tap_file = os.path.join(outdir, 'FakeTestCase.tap') + tap_file = os.path.join(outdir, "FakeTestCase.tap") self.assertTrue(os.path.exists(tap_file)) def test_generates_tap_reports_in_existing_outdir(self): outdir = tempfile.mkdtemp() tracker = Tracker(outdir=outdir) - tracker.add_ok('FakeTestCase', 'I should be in the specified dir.') + tracker.add_ok("FakeTestCase", "I should be in the specified dir.") tracker.generate_tap_reports() - tap_file = os.path.join(outdir, 'FakeTestCase.tap') + tap_file = os.path.join(outdir, "FakeTestCase.tap") self.assertTrue(os.path.exists(tap_file)) def test_results_not_combined_by_default(self): @@ -82,30 +82,31 @@ def test_results_not_combined_by_default(self): def test_individual_report_has_no_plan_when_combined(self): outdir = tempfile.mkdtemp() tracker = Tracker(outdir=outdir, combined=True) - tracker.add_ok('FakeTestCase', 'Look ma, no plan!') + tracker.add_ok("FakeTestCase", "Look ma, no plan!") out_file = StringIO() tracker.generate_tap_report( - 'FakeTestCase', tracker._test_cases['FakeTestCase'], out_file) + "FakeTestCase", tracker._test_cases["FakeTestCase"], out_file + ) report = out_file.getvalue() - self.assertTrue('Look ma' in report) - self.assertFalse('1..' in report) + self.assertTrue("Look ma" in report) + self.assertFalse("1.." in report) - @mock.patch('tap.tracker.ENABLE_VERSION_13', False) + @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_combined_results_in_one_file_tap_version_12(self): outdir = tempfile.mkdtemp() tracker = Tracker(outdir=outdir, combined=True) - tracker.add_ok('FakeTestCase', 'YESSS!') - tracker.add_ok('DifferentFakeTestCase', 'GOAAL!') + tracker.add_ok("FakeTestCase", "YESSS!") + tracker.add_ok("DifferentFakeTestCase", "GOAAL!") tracker.generate_tap_reports() + self.assertFalse(os.path.exists(os.path.join(outdir, "FakeTestCase.tap"))) self.assertFalse( - os.path.exists(os.path.join(outdir, 'FakeTestCase.tap'))) - self.assertFalse( - os.path.exists(os.path.join(outdir, 'DifferentFakeTestCase.tap'))) - with open(os.path.join(outdir, 'testresults.tap'), 'r') as f: + os.path.exists(os.path.join(outdir, "DifferentFakeTestCase.tap")) + ) + with open(os.path.join(outdir, "testresults.tap"), "r") as f: report = f.read() expected = inspect.cleandoc( """{header_1} @@ -114,24 +115,26 @@ def test_combined_results_in_one_file_tap_version_12(self): ok 2 GOAAL! 1..2 """.format( - header_1=self._make_header('FakeTestCase'), - header_2=self._make_header('DifferentFakeTestCase'))) + header_1=self._make_header("FakeTestCase"), + header_2=self._make_header("DifferentFakeTestCase"), + ) + ) self.assertEqual(report.strip(), expected) - @mock.patch('tap.tracker.ENABLE_VERSION_13', True) + @mock.patch("tap.tracker.ENABLE_VERSION_13", True) def test_combined_results_in_one_file_tap_version_13(self): outdir = tempfile.mkdtemp() tracker = Tracker(outdir=outdir, combined=True) - tracker.add_ok('FakeTestCase', 'YESSS!') - tracker.add_ok('DifferentFakeTestCase', 'GOAAL!') + tracker.add_ok("FakeTestCase", "YESSS!") + tracker.add_ok("DifferentFakeTestCase", "GOAAL!") tracker.generate_tap_reports() + self.assertFalse(os.path.exists(os.path.join(outdir, "FakeTestCase.tap"))) self.assertFalse( - os.path.exists(os.path.join(outdir, 'FakeTestCase.tap'))) - self.assertFalse( - os.path.exists(os.path.join(outdir, 'DifferentFakeTestCase.tap'))) - with open(os.path.join(outdir, 'testresults.tap'), 'r') as f: + os.path.exists(os.path.join(outdir, "DifferentFakeTestCase.tap")) + ) + with open(os.path.join(outdir, "testresults.tap"), "r") as f: report = f.read() expected = inspect.cleandoc( """ @@ -142,8 +145,10 @@ def test_combined_results_in_one_file_tap_version_13(self): ok 2 GOAAL! 1..2 """.format( - header_1=self._make_header('FakeTestCase'), - header_2=self._make_header('DifferentFakeTestCase'))) + header_1=self._make_header("FakeTestCase"), + header_2=self._make_header("DifferentFakeTestCase"), + ) + ) self.assertEqual(report.strip(), expected) def test_tracker_does_not_stream_by_default(self): @@ -154,13 +159,13 @@ def test_tracker_has_stream(self): tracker = Tracker() self.assertTrue(tracker.stream is None) - @mock.patch('tap.tracker.ENABLE_VERSION_13', False) + @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_add_ok_writes_to_stream_while_streaming(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream) - tracker.add_ok('FakeTestCase', 'YESSS!') - tracker.add_ok('AnotherTestCase', 'Sure.') + tracker.add_ok("FakeTestCase", "YESSS!") + tracker.add_ok("AnotherTestCase", "Sure.") expected = inspect.cleandoc( """{header_1} @@ -168,50 +173,55 @@ def test_add_ok_writes_to_stream_while_streaming(self): {header_2} ok 2 Sure. """.format( - header_1=self._make_header('FakeTestCase'), - header_2=self._make_header('AnotherTestCase'))) + header_1=self._make_header("FakeTestCase"), + header_2=self._make_header("AnotherTestCase"), + ) + ) self.assertEqual(stream.getvalue().strip(), expected) - @mock.patch('tap.tracker.ENABLE_VERSION_13', False) + @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_add_not_ok_writes_to_stream_while_streaming(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream) - tracker.add_not_ok('FakeTestCase', 'YESSS!') + tracker.add_not_ok("FakeTestCase", "YESSS!") expected = inspect.cleandoc( """{header} not ok 1 YESSS! """.format( - header=self._make_header('FakeTestCase'))) + header=self._make_header("FakeTestCase") + ) + ) self.assertEqual(stream.getvalue().strip(), expected) - @mock.patch('tap.tracker.ENABLE_VERSION_13', False) + @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_add_skip_writes_to_stream_while_streaming(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream) - tracker.add_skip('FakeTestCase', 'YESSS!', 'a reason') + tracker.add_skip("FakeTestCase", "YESSS!", "a reason") expected = inspect.cleandoc( """{header} ok 1 YESSS! # SKIP a reason """.format( - header=self._make_header('FakeTestCase'))) + header=self._make_header("FakeTestCase") + ) + ) self.assertEqual(stream.getvalue().strip(), expected) def test_streaming_does_not_write_files(self): outdir = tempfile.mkdtemp() stream = StringIO() tracker = Tracker(outdir=outdir, streaming=True, stream=stream) - tracker.add_ok('FakeTestCase', 'YESSS!') + tracker.add_ok("FakeTestCase", "YESSS!") tracker.generate_tap_reports() - self.assertFalse( - os.path.exists(os.path.join(outdir, 'FakeTestCase.tap'))) + self.assertFalse(os.path.exists(os.path.join(outdir, "FakeTestCase.tap"))) - @mock.patch('tap.tracker.ENABLE_VERSION_13', False) + @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_streaming_writes_plan(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream) @@ -219,23 +229,23 @@ def test_streaming_writes_plan(self): tracker.generate_tap_reports() - self.assertEqual(stream.getvalue(), '1..42\n') + self.assertEqual(stream.getvalue(), "1..42\n") - @mock.patch('tap.tracker.ENABLE_VERSION_13', False) + @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_write_plan_first_streaming(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream) tracker.set_plan(123) tracker.generate_tap_reports() - self.assertEqual(stream.getvalue(), '1..123\n') + self.assertEqual(stream.getvalue(), "1..123\n") - @mock.patch('tap.tracker.ENABLE_VERSION_13', False) + @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_write_plan_immediate_streaming(self): stream = StringIO() Tracker(streaming=True, stream=stream, plan=123) - self.assertEqual(stream.getvalue(), '1..123\n') + self.assertEqual(stream.getvalue(), "1..123\n") - @mock.patch('tap.tracker.ENABLE_VERSION_13', False) + @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_write_plan_first_combined(self): outdir = tempfile.mkdtemp() tracker = Tracker(streaming=False, outdir=outdir, combined=True) @@ -243,21 +253,21 @@ def test_write_plan_first_combined(self): tracker.generate_tap_reports() with open(os.path.join(outdir, "testresults.tap"), "r") as f: lines = f.readlines() - self.assertEqual(lines[0], '1..123\n') + self.assertEqual(lines[0], "1..123\n") - @mock.patch('tap.tracker.ENABLE_VERSION_13', False) + @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_write_plan_first_not_combined(self): outdir = tempfile.mkdtemp() tracker = Tracker(streaming=False, outdir=outdir, combined=False) with self.assertRaises(ValueError): tracker.set_plan(123) - @mock.patch('tap.tracker.ENABLE_VERSION_13', True) + @mock.patch("tap.tracker.ENABLE_VERSION_13", True) def test_streaming_writes_tap_version_13(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream) - tracker.add_skip('FakeTestCase', 'YESSS!', 'a reason') + tracker.add_skip("FakeTestCase", "YESSS!", "a reason") expected = inspect.cleandoc( """ @@ -265,25 +275,26 @@ def test_streaming_writes_tap_version_13(self): {header} ok 1 YESSS! # SKIP a reason """.format( - header=self._make_header('FakeTestCase'))) + header=self._make_header("FakeTestCase") + ) + ) self.assertEqual(stream.getvalue().strip(), expected) def test_get_default_tap_file_path(self): tracker = Tracker() - file_path = tracker._get_tap_file_path('foo') - self.assertEqual('foo.tap', file_path) + file_path = tracker._get_tap_file_path("foo") + self.assertEqual("foo.tap", file_path) def test_sanitizes_tap_file_path(self): tracker = Tracker() - file_path = tracker._get_tap_file_path('an awful \\ testcase / name\n') - self.assertEqual('an-awful---testcase---name-.tap', file_path) + file_path = tracker._get_tap_file_path("an awful \\ testcase / name\n") + self.assertEqual("an-awful---testcase---name-.tap", file_path) def test_adds_not_ok_with_diagnostics(self): tracker = Tracker() - tracker.add_not_ok( - 'FakeTestCase', 'a description', diagnostics='# more info\n') - line = tracker._test_cases['FakeTestCase'][0] - self.assertEqual('# more info\n', line.diagnostics) + tracker.add_not_ok("FakeTestCase", "a description", diagnostics="# more info\n") + line = tracker._test_cases["FakeTestCase"][0] + self.assertEqual("# more info\n", line.diagnostics) def test_header_displayed_by_default(self): tracker = Tracker() @@ -293,15 +304,16 @@ def test_header_set_by_init(self): tracker = Tracker(header=False) self.assertFalse(tracker.header) - @mock.patch('tap.tracker.ENABLE_VERSION_13', False) + @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_does_not_write_header(self): stream = StringIO() tracker = Tracker(streaming=True, stream=stream, header=False) - tracker.add_skip('FakeTestCase', 'YESSS!', 'a reason') + tracker.add_skip("FakeTestCase", "YESSS!", "a reason") expected = inspect.cleandoc( """ ok 1 YESSS! # SKIP a reason - """) + """ + ) self.assertEqual(stream.getvalue().strip(), expected) diff --git a/tap/tests/testcase.py b/tap/tests/testcase.py index d4134e2..d92fb19 100644 --- a/tap/tests/testcase.py +++ b/tap/tests/testcase.py @@ -6,7 +6,6 @@ class TestCase(unittest.TestCase): - - def __init__(self, methodName='runTest'): + def __init__(self, methodName="runTest"): super(TestCase, self).__init__(methodName) self.factory = Factory() diff --git a/tap/tracker.py b/tap/tracker.py index 693856e..4fa7a6c 100644 --- a/tap/tracker.py +++ b/tap/tracker.py @@ -12,16 +12,22 @@ try: import more_itertools # noqa import yaml # noqa + ENABLE_VERSION_13 = True except ImportError: # pragma: no cover ENABLE_VERSION_13 = False class Tracker(object): - def __init__( - self, outdir=None, combined=False, streaming=False, stream=None, - header=True, plan=None): + self, + outdir=None, + combined=False, + streaming=False, + stream=None, + header=True, + plan=None, + ): self.outdir = outdir # Combine all the test results into one file. @@ -48,9 +54,9 @@ def __init__( # Python versions 2 and 3 keep maketrans in different locations. if sys.version_info[0] < 3: - self._sanitized_table = string.maketrans(' \\/\n', '----') + self._sanitized_table = string.maketrans(" \\/\n", "----") else: # pragma: no cover - self._sanitized_table = str.maketrans(' \\/\n', '----') + self._sanitized_table = str.maketrans(" \\/\n", "----") if self.streaming: self._write_tap_version(self.stream) @@ -77,25 +83,33 @@ def _track(self, class_name): if self.combined: self.combined_test_cases_seen.append(class_name) - def add_ok(self, class_name, description, directive=''): + def add_ok(self, class_name, description, directive=""): result = Result( - ok=True, number=self._get_next_line_number(class_name), - description=description, directive=Directive(directive)) + ok=True, + number=self._get_next_line_number(class_name), + description=description, + directive=Directive(directive), + ) self._add_line(class_name, result) - def add_not_ok( - self, class_name, description, directive='', diagnostics=None): + def add_not_ok(self, class_name, description, directive="", diagnostics=None): result = Result( - ok=False, number=self._get_next_line_number(class_name), - description=description, diagnostics=diagnostics, - directive=Directive(directive)) + ok=False, + number=self._get_next_line_number(class_name), + description=description, + diagnostics=diagnostics, + directive=Directive(directive), + ) self._add_line(class_name, result) def add_skip(self, class_name, description, reason): - directive = 'SKIP {0}'.format(reason) + directive = "SKIP {0}".format(reason) result = Result( - ok=True, number=self._get_next_line_number(class_name), - description=description, directive=Directive(directive)) + ok=True, + number=self._get_next_line_number(class_name), + description=description, + directive=Directive(directive), + ) self._add_line(class_name, result) def _add_line(self, class_name, result): @@ -118,13 +132,13 @@ def _get_next_line_number(self, class_name): return 1 def set_plan(self, total): - """Notify the tracker how many total tests there will be""" + """Notify the tracker how many total tests there will be.""" self.plan = total if self.streaming: - # this will only write the plan if we haven't written it - # already .. but we want to check if we already wrote a + # This will only write the plan if we haven't written it + # already but we want to check if we already wrote a # test out (in which case we can't just write the plan out - # right here..) + # right here). if not self.combined_test_cases_seen: self._write_plan(self.stream) elif not self.combined: @@ -139,31 +153,29 @@ def generate_tap_reports(self): the output file name is generated from the test case. """ # We're streaming but set_plan wasn't called, so we can only - # know the plan now (at the end) + # know the plan now (at the end). if self.streaming and not self._plan_written: - print('1..{0}'.format(self.combined_line_number), file=self.stream) + print("1..{0}".format(self.combined_line_number), file=self.stream) self._plan_written = True return if self.combined: - combined_file = 'testresults.tap' + combined_file = "testresults.tap" if self.outdir: combined_file = os.path.join(self.outdir, combined_file) - with open(combined_file, 'w') as out_file: + with open(combined_file, "w") as out_file: self._write_tap_version(out_file) if self.plan is not None: - print('1..{0}'.format(self.plan), file=out_file) + print("1..{0}".format(self.plan), file=out_file) for test_case in self.combined_test_cases_seen: self.generate_tap_report( - test_case, self._test_cases[test_case], out_file) - if self.plan is None: - print( - '1..{0}'.format(self.combined_line_number), - file=out_file, + test_case, self._test_cases[test_case], out_file ) + if self.plan is None: + print("1..{0}".format(self.combined_line_number), file=out_file) else: for test_case, tap_lines in self._test_cases.items(): - with open(self._get_tap_file_path(test_case), 'w') as out_file: + with open(self._get_tap_file_path(test_case), "w") as out_file: self._write_tap_version(out_file) self.generate_tap_report(test_case, tap_lines, out_file) @@ -176,35 +188,36 @@ def generate_tap_report(self, test_case, tap_lines, out_file): # For combined results, the plan is only output once after # all the test cases complete. if not self.combined: - print('1..{0}'.format(len(tap_lines)), file=out_file) + print("1..{0}".format(len(tap_lines)), file=out_file) def _write_tap_version(self, filename): - """Write a Version 13 TAP row + """Write a Version 13 TAP row. - filename can be a filename or a stream + ``filename`` can be a filename or a stream. """ if ENABLE_VERSION_13: - print('TAP version 13', file=filename) + print("TAP version 13", file=filename) def _write_plan(self, stream): - """Write the plan line to the stream + """Write the plan line to the stream. If we have a plan and have not yet written it out, write it to - the given stream + the given stream. """ if self.plan is not None: if not self._plan_written: - print('1..{0}'.format(self.plan), file=stream) + print("1..{0}".format(self.plan), file=stream) self._plan_written = True def _write_test_case_header(self, test_case, stream): - print(_('# TAP results for {test_case}').format( - test_case=test_case), file=stream) + print( + _("# TAP results for {test_case}").format(test_case=test_case), file=stream + ) def _get_tap_file_path(self, test_case): """Get the TAP output file path for the test case.""" sanitized_test_case = test_case.translate(self._sanitized_table) - tap_file = sanitized_test_case + '.tap' + tap_file = sanitized_test_case + ".tap" if self.outdir: return os.path.join(self.outdir, tap_file) return tap_file diff --git a/tox.ini b/tox.ini index 7551011..86c9023 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,7 @@ envlist = py36 pypy runner - flake8 + lint integration coverage language_ar @@ -41,11 +41,14 @@ deps = mock commands = python tap/tests/run.py -[testenv:flake8] +[testenv:lint] deps = Babel + black flake8 -commands = flake8 tap setup.py transifex.py +commands = + flake8 tap setup.py transifex.py + black --check tap setup.py transifex.py [testenv:integration] deps = diff --git a/transifex.py b/transifex.py index 38b249c..506c17f 100644 --- a/transifex.py +++ b/transifex.py @@ -6,58 +6,45 @@ import requests -API_URL = 'https://www.transifex.com/api/2' -LANGUAGES = [ - 'ar', - 'de', - 'es', - 'fr', - 'it', - 'ja', - 'nl', - 'pt', - 'ru', - 'zh', -] +API_URL = "https://www.transifex.com/api/2" +LANGUAGES = ["ar", "de", "es", "fr", "it", "ja", "nl", "pt", "ru", "zh"] def fetch_po_for(language, username, password): - print 'Downloading po file for {0} ...'.format(language) - po_api = '/project/tappy/resource/tappypot/translation/{0}/'.format( - language) + print("Downloading po file for {0} ...".format(language)) + po_api = "/project/tappy/resource/tappypot/translation/{0}/".format(language) po_url = API_URL + po_api - params = {'file': '1'} + params = {"file": "1"} r = requests.get(po_url, auth=(username, password), params=params) if r.status_code == 200: - r.encoding = 'utf-8' + r.encoding = "utf-8" output_file = os.path.join( - here, 'tap', 'locale', language, 'LC_MESSAGES', 'tappy.po') - with open(output_file, 'wb') as out: - out.write(r.text.encode('utf-8')) + here, "tap", "locale", language, "LC_MESSAGES", "tappy.po" + ) + with open(output_file, "wb") as out: + out.write(r.text.encode("utf-8")) else: - print('Something went wrong fetching the {0} po file.'.format( - language)) + print("Something went wrong fetching the {0} po file.".format(language)) def get_auth_from_conf(here): - transifex_conf = os.path.join(here, '.transifex.ini') + transifex_conf = os.path.join(here, ".transifex.ini") config = ConfigParser() try: - with open(transifex_conf, 'r') as conf: + with open(transifex_conf, "r") as conf: config.readfp(conf) except IOError as ex: - sys.exit('Failed to load authentication configuration file.\n' - '{0}'.format(ex)) + sys.exit("Failed to load authentication configuration file.\n" "{0}".format(ex)) try: - username = config.get('auth', 'username') - password = config.get('auth', 'password') + username = config.get("auth", "username") + password = config.get("auth", "password") except (NoOptionError, NoSectionError) as ex: - sys.exit('Oops. Incomplete configuration file: {0}'.format(ex)) + sys.exit("Oops. Incomplete configuration file: {0}".format(ex)) return username, password -if __name__ == '__main__': +if __name__ == "__main__": here = os.path.abspath(os.path.dirname(__file__)) username, password = get_auth_from_conf(here) From 6989b108f15c1d8b437f3494c0afa1748a477be8 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Fri, 14 Sep 2018 20:03:28 -0400 Subject: [PATCH 09/43] Python 3.7 is not on Travis yet. Switch to 3.6 for lint. --- .travis.yml | 2 +- tox.ini | 51 --------------------------------------------------- 2 files changed, 1 insertion(+), 52 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0c969c9..bc0a06e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ matrix: python: 2.7 env: TOX_ENV=runner - os: linux - python: 3.7 + python: 3.6 env: TOX_ENV=lint - os: linux python: 2.7 diff --git a/tox.ini b/tox.ini index 86c9023..d0d6fe1 100644 --- a/tox.ini +++ b/tox.ini @@ -9,16 +9,6 @@ envlist = lint integration coverage - language_ar - language_de - language_es - language_fr - language_it - language_ja - language_nl - language_pt - language_ru - language_zh [testenv] deps = @@ -75,44 +65,3 @@ commands = coverage run tap/tests/run.py coverage report -m --include "*/tap/*" --omit "*/tests/*" codecov - -# Test that each language's strings contain no errors. -[testenv:language_ar] -setenv = LANGUAGE=ar -commands = pytest {envsitepackagesdir}/tap - -[testenv:language_de] -setenv = LANGUAGE=de -commands = pytest {envsitepackagesdir}/tap - -[testenv:language_es] -setenv = LANGUAGE=es -commands = pytest {envsitepackagesdir}/tap - -[testenv:language_fr] -setenv = LANGUAGE=fr -commands = pytest {envsitepackagesdir}/tap - -[testenv:language_it] -setenv = LANGUAGE=it -commands = pytest {envsitepackagesdir}/tap - -[testenv:language_ja] -setenv = LANGUAGE=ja -commands = pytest {envsitepackagesdir}/tap - -[testenv:language_nl] -setenv = LANGUAGE=nl -commands = pytest {envsitepackagesdir}/tap - -[testenv:language_pt] -setenv = LANGUAGE=pt -commands = pytest {envsitepackagesdir}/tap - -[testenv:language_ru] -setenv = LANGUAGE=ru -commands = pytest {envsitepackagesdir}/tap - -[testenv:language_zh] -setenv = LANGUAGE=zh -commands = pytest {envsitepackagesdir}/tap From 6c37db8e5f2e9ce277587f1251e2942cbb5aa7fe Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Fri, 14 Sep 2018 20:10:19 -0400 Subject: [PATCH 10/43] Set 2.5 release date. --- docs/releases.rst | 6 +++--- tap/__init__.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/releases.rst b/docs/releases.rst index e435c46..b699ff0 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -1,10 +1,10 @@ Releases ======== -Version 2.5, In Development ---------------------------- +Version 2.5, Released September 15, 2018 +---------------------------------------- -* Add `set_plan` to `Tracker` which allows producing the `1..N` line +* Add `set_plan` to `Tracker` which allows producing the `1..N` plan line before any tests. * Switch code style to use Black formatting. diff --git a/tap/__init__.py b/tap/__init__.py index 28fb65f..117c2fe 100644 --- a/tap/__init__.py +++ b/tap/__init__.py @@ -3,4 +3,4 @@ from .runner import TAPTestRunner __all__ = ["TAPTestRunner"] -__version__ = "2.4" +__version__ = "2.5" From fa84f6fa584b761b371e898fc68f978854c79817 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Fri, 14 Sep 2018 20:17:58 -0400 Subject: [PATCH 11/43] Fix release docs notes. --- docs/releases.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases.rst b/docs/releases.rst index b699ff0..5b3472d 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -4,7 +4,7 @@ Releases Version 2.5, Released September 15, 2018 ---------------------------------------- -* Add `set_plan` to `Tracker` which allows producing the `1..N` plan line +* Add ``set_plan`` to ``Tracker`` which allows producing the ``1..N`` plan line before any tests. * Switch code style to use Black formatting. From 31d501fa0f7888a42afe4ce566ee74285b304d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolai=20S=C3=B8borg?= Date: Wed, 3 Oct 2018 13:57:32 +0200 Subject: [PATCH 12/43] Add 'duration_ms' output Build upon #72 from Mikael --- AUTHORS | 2 ++ tap/runner.py | 21 ++++++++++++++++++++- tap/tracker.py | 5 +++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index addfcbc..05d5161 100644 --- a/AUTHORS +++ b/AUTHORS @@ -9,4 +9,6 @@ Contributors * Mark E. Hamilton * Matt Layman * Michael F. Lamb (http://datagrok.org) +* Mikael Barfred +* Nicolai Søborg * Nicolas Caniart diff --git a/tap/runner.py b/tap/runner.py index d32bec1..f90a66a 100644 --- a/tap/runner.py +++ b/tap/runner.py @@ -1,6 +1,8 @@ # Copyright (c) 2018, Matt Layman and contributors +from collections import Counter import os +from time import time from unittest import TextTestResult, TextTestRunner from unittest.runner import _WritelnDecorator import sys @@ -13,9 +15,15 @@ class TAPTestResult(TextTestResult): FORMAT = None + track_duration = False def __init__(self, stream, descriptions, verbosity): super(TAPTestResult, self).__init__(stream, descriptions, verbosity) + self._durationTracker = Counter({'startTime': time()}) + + def startTest(self, test): + super().startTest(test) + self._durationTracker[test.id()] = (time() - self._durationTracker['startTime']) * 1000 def stopTestRun(self): """Once the test run is complete, generate each of the TAP files.""" @@ -49,7 +57,8 @@ def addSubTest(self, test, subtest, err): def addSuccess(self, test): super(TAPTestResult, self).addSuccess(test) - self.tracker.add_ok(self._cls_name(test), self._description(test)) + self.tracker.add_ok(self._cls_name(test), self._description(test), + diagnostics=self._diagnostics(test)) def addSkip(self, test, reason): super(TAPTestResult, self).addSkip(test, reason) @@ -71,6 +80,11 @@ def addUnexpectedSuccess(self, test): def _cls_name(self, test): return test.__class__.__name__ + def _diagnostics(self, test): + if self.track_duration and test.id() in self._durationTracker: + return " ---\n duration_ms: {:0.2f}\n ...\n".format(self._durationTracker[test.id()]) + return None + def _description(self, test): if self.FORMAT: try: @@ -135,6 +149,11 @@ def set_header(cls, header): """Set the header display flag.""" _tracker.header = header + @classmethod + def set_track_duration(cls, track_duration): + """Track test duration (ms) and output in TAP file.""" + TAPTestResult.track_duration = track_duration + @classmethod def set_format(cls, fmt): """Set the format of each test line. diff --git a/tap/tracker.py b/tap/tracker.py index baea295..782dc97 100644 --- a/tap/tracker.py +++ b/tap/tracker.py @@ -62,10 +62,11 @@ def _track(self, class_name): if self.combined: self.combined_test_cases_seen.append(class_name) - def add_ok(self, class_name, description, directive=''): + def add_ok(self, class_name, description, directive='', diagnostics=None): result = Result( ok=True, number=self._get_next_line_number(class_name), - description=description, directive=Directive(directive)) + description=description, diagnostics=diagnostics, + directive=Directive(directive)) self._add_line(class_name, result) def add_not_ok( From ced92c47195e07d391b861ed880cbe80ca4585e0 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Wed, 23 Jan 2019 13:18:41 -0500 Subject: [PATCH 13/43] Add official support for Python 3.7 (#83) * Add official support for Python 3.7. * Try bumping everything to 16.04. * Fix lint error and try pypy3. * Try 3.5 version of pypy. --- .travis.yml | 8 ++++++-- Pipfile | 2 +- README.md | 1 + docs/index.rst | 7 ++++++- docs/releases.rst | 5 +++++ tap/directive.py | 2 +- tox.ini | 2 +- 7 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index bc0a06e..17b64f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ sudo: false language: python +dist: xenial matrix: include: - os: linux @@ -15,8 +16,11 @@ matrix: python: 3.6 env: TOX_ENV=py36 - os: linux - python: pypy - env: TOX_ENV=pypy + python: 3.7 + env: TOX_ENV=py37 + - os: linux + python: pypy3.5 + env: TOX_ENV=pypy3 - os: osx language: generic env: TOX_ENV=py27 diff --git a/Pipfile b/Pipfile index 807e908..5e42886 100644 --- a/Pipfile +++ b/Pipfile @@ -6,7 +6,7 @@ name = "pypi" [dev-packages] Babel = "*" coverage = "*" -"flake8" = "*" +"flake8" = "==3.6.0" mock = "*" requests = "*" Sphinx = "*" diff --git a/README.md b/README.md index 25878e2..95ba38d 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ on Python 3.4, 3.5, 3.6, +3.7, and PyPy. It is continuously tested on Linux, OS X, and Windows. diff --git a/docs/index.rst b/docs/index.rst index 0a68b16..b2f7b73 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,7 +24,12 @@ Installation tappy is available for download from `PyPI `_. tappy is currently supported on -Python 2.7, 3.4, 3.5, 3.6, and PyPy. +Python 2.7, +3.4, +3.5, +3.6, +3.7, +and PyPy. It is continuously tested on Linux, OS X, and Windows. .. code-block:: console diff --git a/docs/releases.rst b/docs/releases.rst index 5b3472d..a6b62fc 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -1,6 +1,11 @@ Releases ======== +Version 2.6, In Development +--------------------------- + +* Add support for Python 3.7. + Version 2.5, Released September 15, 2018 ---------------------------------------- diff --git a/tap/directive.py b/tap/directive.py index df6ab87..9fb6e79 100644 --- a/tap/directive.py +++ b/tap/directive.py @@ -23,7 +23,7 @@ def __init__(self, text): """Initialize the directive by parsing the text. The text is assumed to be everything after a '#\s*' on a result line. - """ + """ # noqa: flake8 is grumbling about the \s in the description. self._text = text self._skip = False self._todo = False diff --git a/tox.ini b/tox.ini index d0d6fe1..9936f18 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ envlist = py34 py35 py36 - pypy + pypy3 runner lint integration From 79a749313c61ea94ee49d67ba6a1534974bc03aa Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Wed, 23 Jan 2019 13:31:18 -0500 Subject: [PATCH 14/43] Update copyright year to 2019. Fixes #81 --- LICENSE | 2 +- docs/conf.py | 2 +- setup.py | 2 +- tap/__init__.py | 2 +- tap/adapter.py | 2 +- tap/directive.py | 2 +- tap/i18n.py | 2 +- tap/line.py | 2 +- tap/loader.py | 2 +- tap/main.py | 2 +- tap/parser.py | 2 +- tap/rules.py | 2 +- tap/runner.py | 2 +- tap/tests/__init__.py | 2 +- tap/tests/factory.py | 2 +- tap/tests/run.py | 2 +- tap/tests/test_adapter.py | 2 +- tap/tests/test_directive.py | 2 +- tap/tests/test_line.py | 2 +- tap/tests/test_loader.py | 2 +- tap/tests/test_main.py | 2 +- tap/tests/test_parser.py | 2 +- tap/tests/test_result.py | 2 +- tap/tests/test_rules.py | 2 +- tap/tests/test_runner.py | 2 +- tap/tests/test_tracker.py | 2 +- tap/tests/testcase.py | 2 +- tap/tracker.py | 2 +- transifex.py | 2 +- 29 files changed, 29 insertions(+), 29 deletions(-) diff --git a/LICENSE b/LICENSE index 443a83f..852b81e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018, Matt Layman and contributors. See AUTHORS for more details. +Copyright (c) 2019, Matt Layman and contributors. See AUTHORS for more details. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/docs/conf.py b/docs/conf.py index ec1f6d2..23e156a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -52,7 +52,7 @@ # General information about the project. project = u'tappy' -copyright = u'2018, Matt Layman and contributors' +copyright = u'2019, Matt Layman and contributors' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/setup.py b/setup.py index 83188c4..50832e0 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors """ tappy is a set of tools for working with the `Test Anything Protocol (TAP) `_, a line based test protocol for recording test diff --git a/tap/__init__.py b/tap/__init__.py index 117c2fe..64438f0 100644 --- a/tap/__init__.py +++ b/tap/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors from .runner import TAPTestRunner diff --git a/tap/adapter.py b/tap/adapter.py index 3311667..7f76279 100644 --- a/tap/adapter.py +++ b/tap/adapter.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors class Adapter(object): diff --git a/tap/directive.py b/tap/directive.py index 9fb6e79..f946201 100644 --- a/tap/directive.py +++ b/tap/directive.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors import re diff --git a/tap/i18n.py b/tap/i18n.py index a3798db..ad106ca 100644 --- a/tap/i18n.py +++ b/tap/i18n.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors import gettext import os diff --git a/tap/line.py b/tap/line.py index 8ae1c5e..0d44ff8 100644 --- a/tap/line.py +++ b/tap/line.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors try: import yaml diff --git a/tap/loader.py b/tap/loader.py index 10d0ab5..b07b567 100644 --- a/tap/loader.py +++ b/tap/loader.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors import os import unittest diff --git a/tap/main.py b/tap/main.py index 85d049f..9da91a9 100644 --- a/tap/main.py +++ b/tap/main.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors import argparse import sys diff --git a/tap/parser.py b/tap/parser.py index e79575c..4569042 100644 --- a/tap/parser.py +++ b/tap/parser.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors from io import StringIO import itertools diff --git a/tap/rules.py b/tap/rules.py index c00af19..bd4df23 100644 --- a/tap/rules.py +++ b/tap/rules.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors from tap.adapter import Adapter from tap.directive import Directive diff --git a/tap/runner.py b/tap/runner.py index 7bc2e02..94134e1 100644 --- a/tap/runner.py +++ b/tap/runner.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors import os from unittest import TextTestResult, TextTestRunner diff --git a/tap/tests/__init__.py b/tap/tests/__init__.py index 7d168a3..44fdf65 100644 --- a/tap/tests/__init__.py +++ b/tap/tests/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors """Tests for tappy""" from tap.tests.testcase import TestCase # NOQA diff --git a/tap/tests/factory.py b/tap/tests/factory.py index c550549..3c6308d 100644 --- a/tap/tests/factory.py +++ b/tap/tests/factory.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors import sys import tempfile diff --git a/tap/tests/run.py b/tap/tests/run.py index ec6993f..c781097 100644 --- a/tap/tests/run.py +++ b/tap/tests/run.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors import os import unittest diff --git a/tap/tests/test_adapter.py b/tap/tests/test_adapter.py index 916b459..168372b 100644 --- a/tap/tests/test_adapter.py +++ b/tap/tests/test_adapter.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors try: from unittest import mock diff --git a/tap/tests/test_directive.py b/tap/tests/test_directive.py index 8109a29..890e81a 100644 --- a/tap/tests/test_directive.py +++ b/tap/tests/test_directive.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors import unittest diff --git a/tap/tests/test_line.py b/tap/tests/test_line.py index 4ab15a5..3410e41 100644 --- a/tap/tests/test_line.py +++ b/tap/tests/test_line.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors import unittest diff --git a/tap/tests/test_loader.py b/tap/tests/test_loader.py index 027c380..e6e6199 100644 --- a/tap/tests/test_loader.py +++ b/tap/tests/test_loader.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors import inspect from io import StringIO diff --git a/tap/tests/test_main.py b/tap/tests/test_main.py index b39e8c8..0587ead 100644 --- a/tap/tests/test_main.py +++ b/tap/tests/test_main.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors import argparse import os diff --git a/tap/tests/test_parser.py b/tap/tests/test_parser.py index 5eef987..93a01a5 100644 --- a/tap/tests/test_parser.py +++ b/tap/tests/test_parser.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors from contextlib import contextmanager import inspect diff --git a/tap/tests/test_result.py b/tap/tests/test_result.py index 06fe56c..06fb7e1 100644 --- a/tap/tests/test_result.py +++ b/tap/tests/test_result.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors import os import unittest diff --git a/tap/tests/test_rules.py b/tap/tests/test_rules.py index e90ec1e..9234689 100644 --- a/tap/tests/test_rules.py +++ b/tap/tests/test_rules.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors import unittest diff --git a/tap/tests/test_runner.py b/tap/tests/test_runner.py index 32eea8c..faece59 100644 --- a/tap/tests/test_runner.py +++ b/tap/tests/test_runner.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors import os import sys diff --git a/tap/tests/test_tracker.py b/tap/tests/test_tracker.py index 0368470..915774a 100644 --- a/tap/tests/test_tracker.py +++ b/tap/tests/test_tracker.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors import inspect import os diff --git a/tap/tests/testcase.py b/tap/tests/testcase.py index d92fb19..3a41ece 100644 --- a/tap/tests/testcase.py +++ b/tap/tests/testcase.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors import unittest diff --git a/tap/tracker.py b/tap/tracker.py index 4fa7a6c..2429f20 100644 --- a/tap/tracker.py +++ b/tap/tracker.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors from __future__ import print_function import os diff --git a/transifex.py b/transifex.py index 506c17f..91dd787 100644 --- a/transifex.py +++ b/transifex.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018, Matt Layman and contributors +# Copyright (c) 2019, Matt Layman and contributors from ConfigParser import ConfigParser, NoOptionError, NoSectionError import os From e5d0ec6ebb274ff4359ab4d485ca4bd2c2313c0d Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Mon, 16 Sep 2019 21:42:24 -0400 Subject: [PATCH 15/43] Drop 3.4 support and prep 2.6 release. (#86) * Drop 3.4 support and prep 2.6 release. * Try a Windows specific environment. * What's in site-packages? * Make sure the command runs. * The "I don't care anymore version." --- README.md | 1 - appveyor.yml | 6 +++--- docs/index.rst | 1 - docs/releases.rst | 1 + setup.py | 2 +- tox.ini | 9 ++++++++- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 95ba38d..1ae38bc 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,6 @@ Installation tappy is available for download from [PyPI][pypi]. tappy is currently supported on Python 2.7, -3.4, 3.5, 3.6, 3.7, diff --git a/appveyor.yml b/appveyor.yml index dbe0150..e353dee 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,10 +1,10 @@ environment: - PYTHON: "C:\\Python34" - PATH: "C:\\Python34;C:\\Python34\\Scripts;%PATH%" + PYTHON: "C:\\Python36" + PATH: "C:\\Python36;C:\\Python36\\Scripts;%PATH%" install: - python -m ensurepip - pip install Babel tox build_script: - python --version test_script: - - tox -e py34 + - tox -e windows diff --git a/docs/index.rst b/docs/index.rst index b2f7b73..8e922ba 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -25,7 +25,6 @@ Installation tappy is available for download from `PyPI `_. tappy is currently supported on Python 2.7, -3.4, 3.5, 3.6, 3.7, diff --git a/docs/releases.rst b/docs/releases.rst index a6b62fc..bda307a 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -5,6 +5,7 @@ Version 2.6, In Development --------------------------- * Add support for Python 3.7. +* Drop support for Python 3.4 (it is end-of-life). Version 2.5, Released September 15, 2018 ---------------------------------------- diff --git a/setup.py b/setup.py index 50832e0..4021840 100644 --- a/setup.py +++ b/setup.py @@ -67,9 +67,9 @@ def run(self): "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Testing", ], diff --git a/tox.ini b/tox.ini index 9936f18..25f2298 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,6 @@ [tox] envlist = py27 - py34 py35 py36 pypy3 @@ -17,6 +16,14 @@ deps = pytest commands = pytest {envsitepackagesdir}/tap +[testenv:windows] +basepython = python3.6 +deps = + Babel + mock + pytest +commands = pytest + [testenv:with_optional] deps = Babel From 33a4410b4f177e1fd989a4d62c9b1f30afa19e8a Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Mon, 16 Sep 2019 21:47:28 -0400 Subject: [PATCH 16/43] Bump to 2.6 --- docs/releases.rst | 4 ++-- tap/__init__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/releases.rst b/docs/releases.rst index bda307a..272ee83 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -1,8 +1,8 @@ Releases ======== -Version 2.6, In Development ---------------------------- +Version 2.6, Released September 16, 2019 +---------------------------------------- * Add support for Python 3.7. * Drop support for Python 3.4 (it is end-of-life). diff --git a/tap/__init__.py b/tap/__init__.py index 64438f0..80f620e 100644 --- a/tap/__init__.py +++ b/tap/__init__.py @@ -3,4 +3,4 @@ from .runner import TAPTestRunner __all__ = ["TAPTestRunner"] -__version__ = "2.5" +__version__ = "2.6" From 83b5335f6f104cde2396dbf9dce5ceb3dd5049c3 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Tue, 17 Sep 2019 09:26:30 -0400 Subject: [PATCH 17/43] Switch to using the next function for peekable. (#88) Fixes #87 --- script.py | 3 +++ tap/parser.py | 6 +++--- tap/tests/run.py | 5 ++++- tap/tests/test_parser.py | 6 +++--- 4 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 script.py diff --git a/script.py b/script.py new file mode 100644 index 0000000..cfe5374 --- /dev/null +++ b/script.py @@ -0,0 +1,3 @@ +import sys + +sys.exit(False) diff --git a/tap/parser.py b/tap/parser.py index 4569042..9ad7bee 100644 --- a/tap/parser.py +++ b/tap/parser.py @@ -183,12 +183,12 @@ def _extract_yaml_block(self, indent, fh): raw_yaml = [] indent_match = re.compile(r"^{}".format(indent)) try: - fh.next() + next(fh) while indent_match.match(fh.peek()): - raw_yaml.append(fh.next().replace(indent, "", 1)) + raw_yaml.append(next(fh).replace(indent, "", 1)) # check for the end and stop adding yaml if encountered if self.yaml_block_end.match(fh.peek()): - fh.next() + next(fh) break except StopIteration: pass diff --git a/tap/tests/run.py b/tap/tests/run.py index c781097..1e47fca 100644 --- a/tap/tests/run.py +++ b/tap/tests/run.py @@ -1,6 +1,7 @@ # Copyright (c) 2019, Matt Layman and contributors import os +import sys import unittest from tap import TAPTestRunner @@ -12,4 +13,6 @@ runner = TAPTestRunner() runner.set_outdir("testout") runner.set_format("Hi: {method_name} - {short_description}") - runner.run(tests) + result = runner.run(tests) + status = 0 if result.wasSuccessful() else 1 + sys.exit(status) diff --git a/tap/tests/test_parser.py b/tap/tests/test_parser.py index 93a01a5..cf888a3 100644 --- a/tap/tests/test_parser.py +++ b/tap/tests/test_parser.py @@ -239,7 +239,7 @@ def test_parses_yaml(self): import yaml from more_itertools import peekable # noqa - converted_yaml = yaml.load(u"""test: sample yaml""") + converted_yaml = yaml.safe_load(u"""test: sample yaml""") self.assertEqual(4, len(lines)) self.assertEqual(13, lines[0].version) self.assertEqual(converted_yaml, lines[2].yaml_block) @@ -271,7 +271,7 @@ def test_parses_yaml_no_end(self): import yaml from more_itertools import peekable # noqa - converted_yaml = yaml.load(u"""test: sample yaml""") + converted_yaml = yaml.safe_load(u"""test: sample yaml""") self.assertEqual(4, len(lines)) self.assertEqual(13, lines[0].version) self.assertEqual(converted_yaml, lines[2].yaml_block) @@ -308,7 +308,7 @@ def test_parses_yaml_more_complex(self): import yaml from more_itertools import peekable # noqa - converted_yaml = yaml.load( + converted_yaml = yaml.safe_load( u""" message: test severity: fail From c446fc3b1f7f9a15552d67835f5d82ad20fd0a3d Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Tue, 17 Sep 2019 09:33:28 -0400 Subject: [PATCH 18/43] Bump to 2.6.1. --- docs/releases.rst | 5 +++++ tap/__init__.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/releases.rst b/docs/releases.rst index 272ee83..5367c17 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -1,6 +1,11 @@ Releases ======== +Version 2.6.1, Released September 17, 2019 +------------------------------------------ + +* Fix TAP version 13 support from more-itertools behavior change. + Version 2.6, Released September 16, 2019 ---------------------------------------- diff --git a/tap/__init__.py b/tap/__init__.py index 80f620e..80475a4 100644 --- a/tap/__init__.py +++ b/tap/__init__.py @@ -3,4 +3,4 @@ from .runner import TAPTestRunner __all__ = ["TAPTestRunner"] -__version__ = "2.6" +__version__ = "2.6.1" From a6833bb49309991fb4b05b2879b49a916c1e12a7 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Sun, 20 Oct 2019 17:14:11 -0400 Subject: [PATCH 19/43] Remove the ability to run `python setup.py test`. (#91) `python setup.py test` is not the way we recommend running tests in the contributing guides and it was broken anyway. Fixes #85 --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 4021840..4dd566f 100644 --- a/setup.py +++ b/setup.py @@ -75,6 +75,4 @@ def run(self): ], keywords=["TAP", "unittest"], cmdclass={"build_py": BuildPy, "sdist": Sdist}, - test_suite="tap.tests", - tests_require=["mock"], ) From 088a99af54b87cc50a92d5a19dde0c1f0e8074cb Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Sun, 20 Oct 2019 17:17:39 -0400 Subject: [PATCH 20/43] Add a code of conduct. --- CODE_OF_CONDUCT.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..3b76411 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +As a Python project, +tappy adheres +to the [PSF Code of Conduct](https://www.python.org/psf/conduct/). From d0085a67956f203d30f668a23da9beb4a17882c6 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Sun, 20 Oct 2019 17:23:37 -0400 Subject: [PATCH 21/43] Add a pull request template. --- .github/pull_request_template.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..072c25e --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,5 @@ +To accept your contribution, please ensure that the checklist below is complete. + +* [ ] Is your name/identity in the AUTHORS file? +* [ ] Does the code change (if the PR contains code) have 100% test coverage? +* [ ] Is CI passing all quality and testing checks? From 6d8af6c204f1811c0a15ca47a550461a3f0f7881 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Sun, 20 Oct 2019 17:24:57 -0400 Subject: [PATCH 22/43] Streaming mode should always abort from writing files. (#90) --- tap/directive.py | 4 ++-- tap/tests/test_tracker.py | 11 +++++++++-- tap/tracker.py | 11 ++++++----- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/tap/directive.py b/tap/directive.py index f946201..c114309 100644 --- a/tap/directive.py +++ b/tap/directive.py @@ -20,10 +20,10 @@ class Directive(object): ) def __init__(self, text): - """Initialize the directive by parsing the text. + r"""Initialize the directive by parsing the text. The text is assumed to be everything after a '#\s*' on a result line. - """ # noqa: flake8 is grumbling about the \s in the description. + """ self._text = text self._skip = False self._todo = False diff --git a/tap/tests/test_tracker.py b/tap/tests/test_tracker.py index 915774a..592bd9c 100644 --- a/tap/tests/test_tracker.py +++ b/tap/tests/test_tracker.py @@ -233,11 +233,18 @@ def test_streaming_writes_plan(self): @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_write_plan_first_streaming(self): + outdir = tempfile.mkdtemp() stream = StringIO() - tracker = Tracker(streaming=True, stream=stream) + tracker = Tracker(outdir=outdir, streaming=True, stream=stream) tracker.set_plan(123) + tracker.add_ok("FakeTestCase", "YESSS!") + tracker.generate_tap_reports() - self.assertEqual(stream.getvalue(), "1..123\n") + + self.assertEqual( + stream.getvalue(), "1..123\n# TAP results for FakeTestCase\nok 1 YESSS!\n" + ) + self.assertFalse(os.path.exists(os.path.join(outdir, "FakeTestCase.tap"))) @mock.patch("tap.tracker.ENABLE_VERSION_13", False) def test_write_plan_immediate_streaming(self): diff --git a/tap/tracker.py b/tap/tracker.py index 2429f20..04a1ab8 100644 --- a/tap/tracker.py +++ b/tap/tracker.py @@ -152,11 +152,12 @@ def generate_tap_reports(self): The results are either combined into a single output file or the output file name is generated from the test case. """ - # We're streaming but set_plan wasn't called, so we can only - # know the plan now (at the end). - if self.streaming and not self._plan_written: - print("1..{0}".format(self.combined_line_number), file=self.stream) - self._plan_written = True + if self.streaming: + # We're streaming but set_plan wasn't called, so we can only + # know the plan now (at the end). + if not self._plan_written: + print("1..{0}".format(self.combined_line_number), file=self.stream) + self._plan_written = True return if self.combined: From d6a5dbeae540915bb547aaedf789902d2d53e63b Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Sun, 20 Oct 2019 17:30:00 -0400 Subject: [PATCH 23/43] Committed a test file by accident. --- script.py | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 script.py diff --git a/script.py b/script.py deleted file mode 100644 index cfe5374..0000000 --- a/script.py +++ /dev/null @@ -1,3 +0,0 @@ -import sys - -sys.exit(False) From 3d73aad083d56a888495f7056f4aff2b94dc4af2 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Sun, 20 Oct 2019 17:33:56 -0400 Subject: [PATCH 24/43] Set 2.6.2 release date. --- docs/releases.rst | 6 ++++++ tap/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/releases.rst b/docs/releases.rst index 5367c17..a43bccb 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -1,6 +1,12 @@ Releases ======== +Version 2.6.2, Released October 20, 2019 +---------------------------------------- + +* Fix bug in streaming mode that would generate tap files + when the plan was already set (affected pytest). + Version 2.6.1, Released September 17, 2019 ------------------------------------------ diff --git a/tap/__init__.py b/tap/__init__.py index 80475a4..d12e85d 100644 --- a/tap/__init__.py +++ b/tap/__init__.py @@ -3,4 +3,4 @@ from .runner import TAPTestRunner __all__ = ["TAPTestRunner"] -__version__ = "2.6.1" +__version__ = "2.6.2" From bbc68bc1195786eca1b0dac0674c64be2e0f0f67 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Mon, 9 Dec 2019 20:50:57 -0500 Subject: [PATCH 25/43] Drop support for Python 2. (#95) * Drop support for Python 2. Fixes #94 * Remove unused import. --- .travis.yml | 27 ++++++++++++--------------- README.md | 1 - docs/index.rst | 2 +- docs/releases.rst | 5 +++++ setup.py | 1 - tap/__init__.py | 2 +- tap/tracker.py | 9 +-------- tox.ini | 1 - 8 files changed, 20 insertions(+), 28 deletions(-) diff --git a/.travis.yml b/.travis.yml index 17b64f6..0d8371a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,6 @@ language: python dist: xenial matrix: include: - - os: linux - python: 2.7 - env: TOX_ENV=py27 - os: linux python: 3.4 env: TOX_ENV=py34 @@ -21,28 +18,28 @@ matrix: - os: linux python: pypy3.5 env: TOX_ENV=pypy3 - - os: osx - language: generic - env: TOX_ENV=py27 - before_install: - - brew update - - brew upgrade python - - python3 -m venv venv - - source venv/bin/activate + # - os: osx + # language: generic + # env: TOX_ENV=py27 + # before_install: + # - brew update + # - brew upgrade python + # - python3 -m venv venv + # - source venv/bin/activate - os: linux python: 3.6 env: TOX_ENV=with_optional - os: linux - python: 2.7 + python: 3.7 env: TOX_ENV=runner - os: linux - python: 3.6 + python: 3.7 env: TOX_ENV=lint - os: linux - python: 2.7 + python: 3.7 env: TOX_ENV=integration - os: linux - python: 2.7 + python: 3.7 env: TOX_ENV=coverage install: - pip install Babel tox diff --git a/README.md b/README.md index 1ae38bc..365f5a4 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,6 @@ Installation tappy is available for download from [PyPI][pypi]. tappy is currently supported on Python -2.7, 3.5, 3.6, 3.7, diff --git a/docs/index.rst b/docs/index.rst index 8e922ba..b22a68e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,7 +24,7 @@ Installation tappy is available for download from `PyPI `_. tappy is currently supported on -Python 2.7, +Python 3.5, 3.6, 3.7, diff --git a/docs/releases.rst b/docs/releases.rst index a43bccb..b04ab19 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -1,6 +1,11 @@ Releases ======== +Version 3.0, To Be Released +--------------------------- + +* Drop support for Python 2 (it is end-of-life). + Version 2.6.2, Released October 20, 2019 ---------------------------------------- diff --git a/setup.py b/setup.py index 4dd566f..25563dd 100644 --- a/setup.py +++ b/setup.py @@ -66,7 +66,6 @@ def run(self): "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", diff --git a/tap/__init__.py b/tap/__init__.py index d12e85d..613a61a 100644 --- a/tap/__init__.py +++ b/tap/__init__.py @@ -3,4 +3,4 @@ from .runner import TAPTestRunner __all__ = ["TAPTestRunner"] -__version__ = "2.6.2" +__version__ = "3.0" diff --git a/tap/tracker.py b/tap/tracker.py index 04a1ab8..90d726d 100644 --- a/tap/tracker.py +++ b/tap/tracker.py @@ -1,9 +1,6 @@ # Copyright (c) 2019, Matt Layman and contributors -from __future__ import print_function import os -import string -import sys from tap.directive import Directive from tap.i18n import _ @@ -52,11 +49,7 @@ def __init__( # Internal state for tracking each test case. self._test_cases = {} - # Python versions 2 and 3 keep maketrans in different locations. - if sys.version_info[0] < 3: - self._sanitized_table = string.maketrans(" \\/\n", "----") - else: # pragma: no cover - self._sanitized_table = str.maketrans(" \\/\n", "----") + self._sanitized_table = str.maketrans(" \\/\n", "----") if self.streaming: self._write_tap_version(self.stream) diff --git a/tox.ini b/tox.ini index 25f2298..00c7dce 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,5 @@ [tox] envlist = - py27 py35 py36 pypy3 From 1c079e9ad6aaecaefcfc65d306417b28bce2ce06 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Mon, 9 Dec 2019 20:58:45 -0500 Subject: [PATCH 26/43] Use Python 3 for the macOS build. (#93) * Use Python 3 for the macOS build. * Let's try to simplify based on the Travis CI docs. * Try to stick with pip instead of pip3. --- .travis.yml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0d8371a..7822ab6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,14 +18,10 @@ matrix: - os: linux python: pypy3.5 env: TOX_ENV=pypy3 - # - os: osx - # language: generic - # env: TOX_ENV=py27 - # before_install: - # - brew update - # - brew upgrade python - # - python3 -m venv venv - # - source venv/bin/activate + - os: osx + osx_image: xcode11.2 + language: generic + env: TOX_ENV=py37 - os: linux python: 3.6 env: TOX_ENV=with_optional @@ -42,5 +38,5 @@ matrix: python: 3.7 env: TOX_ENV=coverage install: - - pip install Babel tox + - pip3 install Babel tox script: tox -e $TOX_ENV From f4fe57edd05fb2610b991d002b98f26e5ae11d22 Mon Sep 17 00:00:00 2001 From: Erik Cederstrand Date: Wed, 11 Dec 2019 14:44:40 +0100 Subject: [PATCH 27/43] Add support for subtests (#92) * Support subtests * Add myself to AUTHORS * Just use a simple dict - _SubTest works fine with it, and _OrderedChainMap is not available in all Python versions * Better mocking of subTest context manager * Skip test for Python 2 * Blackify * Ignore coverage on method that is only supportd on Python 3 * Align method names with the rest of the class * Move name to alphabetical entry. * Enable coverage and remove Python2 crutches now that we're Python3-only * Document new subtest support * Remove unused import * Implment suggested assert change --- AUTHORS | 1 + docs/releases.rst | 1 + tap/runner.py | 12 ++++++++++++ tap/tests/test_result.py | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+) diff --git a/AUTHORS b/AUTHORS index 566404e..06d46c5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,6 +5,7 @@ Contributors * Andrew McNamara * Chris Clarke +* Erik Cederstrand * Marc Abramowitz * Mark E. Hamilton * Matt Layman diff --git a/docs/releases.rst b/docs/releases.rst index b04ab19..ee84c7e 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -5,6 +5,7 @@ Version 3.0, To Be Released --------------------------- * Drop support for Python 2 (it is end-of-life). +* Add support for subtests. Version 2.6.2, Released October 20, 2019 ---------------------------------------- diff --git a/tap/runner.py b/tap/runner.py index 94134e1..10e8510 100644 --- a/tap/runner.py +++ b/tap/runner.py @@ -17,6 +17,18 @@ class TAPTestResult(TextTestResult): def __init__(self, stream, descriptions, verbosity): super(TAPTestResult, self).__init__(stream, descriptions, verbosity) + def addSubTest(self, test, subtest, err): + super(TAPTestResult, self).addSubTest(test, subtest, err) + if err is not None: + diagnostics = formatter.format_exception(err) + self.tracker.add_not_ok( + self._cls_name(test), + self._description(subtest), + diagnostics=diagnostics, + ) + else: + self.tracker.add_ok(self._cls_name(test), self._description(subtest)) + def stopTestRun(self): """Once the test run is complete, generate each of the TAP files.""" super(TAPTestResult, self).stopTestRun() diff --git a/tap/tests/test_result.py b/tap/tests/test_result.py index 06fb7e1..b5c1c9a 100644 --- a/tap/tests/test_result.py +++ b/tap/tests/test_result.py @@ -1,7 +1,9 @@ # Copyright (c) 2019, Matt Layman and contributors +import contextlib import os import unittest +import unittest.case from tap.i18n import _ from tap.runner import TAPTestResult @@ -13,6 +15,14 @@ class FakeTestCase(unittest.TestCase): def runTest(self): pass + @contextlib.contextmanager + def subTest(self, *args, **kwargs): + try: + self._subtest = unittest.case._SubTest(self, object(), {}) + yield + finally: + self._subtest = None + def __call__(self, result): pass @@ -70,3 +80,25 @@ def test_adds_unexpected_success(self): self.assertEqual( line.directive.text, "TODO {}".format(_("(unexpected success)")) ) + + def test_adds_subtest_success(self): + """Test that the runner handles subtest success results.""" + result = self._make_one() + test = FakeTestCase() + with test.subTest(): + result.addSubTest(test, test._subtest, None) + line = result.tracker._test_cases["FakeTestCase"][0] + self.assertTrue(line.ok) + + def test_adds_subtest_failure(self): + """Test that the runner handles subtest failure results.""" + result = self._make_one() + # Python 3 does some extra testing in unittest on exceptions so fake + # the cause as if it were raised. + ex = Exception() + ex.__cause__ = None + test = FakeTestCase() + with test.subTest(): + result.addSubTest(test, test._subtest, (ex.__class__, ex, None)) + line = result.tracker._test_cases["FakeTestCase"][0] + self.assertFalse(line.ok) From eeac9630260144b59b4b568e8bc22878e3677ec9 Mon Sep 17 00:00:00 2001 From: Erik Cederstrand Date: Thu, 12 Dec 2019 15:24:32 +0100 Subject: [PATCH 28/43] Let source packages also contain mo files (#97) * Let source packages also contain mo files This removes the dependency on Babel for source-based package installation. * Remove whitespace * Remove Babel from deps * Remove Babel from install deps --- .travis.yml | 2 +- setup.py | 34 +++++++++++++++++++--------------- tox.ini | 7 ------- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7822ab6..2196672 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,5 +38,5 @@ matrix: python: 3.7 env: TOX_ENV=coverage install: - - pip3 install Babel tox + - pip3 install tox script: tox -e $TOX_ENV diff --git a/setup.py b/setup.py index 25563dd..2d001da 100644 --- a/setup.py +++ b/setup.py @@ -9,29 +9,33 @@ `Read the Docs `_. """ -from setuptools import find_packages, setup -from setuptools.command.build_py import build_py -from setuptools.command.sdist import sdist +from setuptools import find_packages, setup, Command import tap -class BuildPy(build_py): - """Custom ``build_py`` command to always build mo files for wheels.""" - - def run(self): - self.run_command("compile_catalog") - # build_py is an old style class so super cannot be used. - build_py.run(self) +class ReleaseCommand(Command): + description = "generate distribution release artifacts" + user_options = [] + def initialize_options(self): + """Initialize options. + This method overrides a required abstract method. + """ -class Sdist(sdist): - """Custom ``sdist`` command to ensure that mo files are always created.""" + def finalize_options(self): + """Finalize options. + This method overrides a required abstract method. + """ def run(self): + """Generate the distribution release artifacts. + The custom command is used to ensure that compiling + po to mo is not skipped. + """ self.run_command("compile_catalog") - # sdist is an old style class so super cannot be used. - sdist.run(self) + self.run_command("sdist") + self.run_command("bdist_wheel") # The docs import setup.py for the version so only call setup when not behaving @@ -73,5 +77,5 @@ def run(self): "Topic :: Software Development :: Testing", ], keywords=["TAP", "unittest"], - cmdclass={"build_py": BuildPy, "sdist": Sdist}, + cmdclass={"release": ReleaseCommand}, ) diff --git a/tox.ini b/tox.ini index 00c7dce..903f1b9 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,6 @@ envlist = [testenv] deps = - Babel mock pytest commands = pytest {envsitepackagesdir}/tap @@ -18,14 +17,12 @@ commands = pytest {envsitepackagesdir}/tap [testenv:windows] basepython = python3.6 deps = - Babel mock pytest commands = pytest [testenv:with_optional] deps = - Babel mock pyyaml more-itertools @@ -33,13 +30,11 @@ commands = python tap/tests/run.py [testenv:runner] deps = - Babel mock commands = python tap/tests/run.py [testenv:lint] deps = - Babel black flake8 commands = @@ -48,7 +43,6 @@ commands = [testenv:integration] deps = - Babel mock pytest pytest-tap @@ -61,7 +55,6 @@ setenv = CI = true passenv = TRAVIS* deps = - Babel coverage mock codecov From 2b21f2866f0c61d1fc73104fcc579ef8c1b2c34b Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Thu, 12 Dec 2019 09:25:53 -0500 Subject: [PATCH 29/43] Remove Babel from Windows CI --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index e353dee..bd73777 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,7 +3,7 @@ environment: PATH: "C:\\Python36;C:\\Python36\\Scripts;%PATH%" install: - python -m ensurepip - - pip install Babel tox + - pip install tox build_script: - python --version test_script: From f01f287af1aa31bdfc8c8219e76ef9fa3bae6c8f Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Sat, 14 Dec 2019 19:44:38 -0500 Subject: [PATCH 30/43] Remove 3.4 from Travis build matrix. --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2196672..2eab2ca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,6 @@ language: python dist: xenial matrix: include: - - os: linux - python: 3.4 - env: TOX_ENV=py34 - os: linux python: 3.5 env: TOX_ENV=py35 From 3794179fdc29987fe6d898d3c41a84a5f14cc32e Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Sat, 14 Dec 2019 20:05:10 -0500 Subject: [PATCH 31/43] Remove mock and use the version in unittest. (#99) --- Pipfile | 1 - Pipfile.lock | 499 +++++++++++++++++++++++++------------- tap/tests/test_adapter.py | 5 +- tap/tests/test_loader.py | 6 +- tap/tests/test_main.py | 6 +- tap/tests/test_parser.py | 6 +- tap/tests/test_runner.py | 6 +- tap/tests/test_tracker.py | 12 +- tox.ini | 7 - 9 files changed, 340 insertions(+), 208 deletions(-) diff --git a/Pipfile b/Pipfile index 5e42886..153fe19 100644 --- a/Pipfile +++ b/Pipfile @@ -7,7 +7,6 @@ name = "pypi" Babel = "*" coverage = "*" "flake8" = "==3.6.0" -mock = "*" requests = "*" Sphinx = "*" tox = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 21f88e3..65fd892 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "75d157b7787ee1eff10691e6cbbd12b52687ef68f2aa6509d18f8a829f166beb" + "sha256": "0849fd48a1c241dba3236f10ab68ec946acd2bd1e48a0500debc97d83de7131f" }, "pipfile-spec": 6, "requires": {}, @@ -17,10 +17,10 @@ "develop": { "alabaster": { "hashes": [ - "sha256:674bb3bab080f598371f4443c5008cbfeb1a5e622dd312395d2d82af2c54c456", - "sha256:b63b1f4dc77c074d386752ec4a8a7517600f6c0db8cd42980cae17ab7b3275d7" + "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", + "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02" ], - "version": "==0.7.11" + "version": "==0.7.12" }, "appdirs": { "hashes": [ @@ -29,43 +29,44 @@ ], "version": "==1.4.3" }, - "atomicwrites": { - "hashes": [ - "sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", - "sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee" - ], - "markers": "python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'", - "version": "==1.2.1" - }, "attrs": { "hashes": [ - "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", - "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb" + "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", + "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" ], - "version": "==18.2.0" + "markers": "python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7'", + "version": "==19.3.0" }, "babel": { "hashes": [ - "sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669", - "sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23" + "sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", + "sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28" ], "index": "pypi", - "version": "==2.6.0" + "version": "==2.7.0" }, "black": { "hashes": [ - "sha256:22158b89c1a6b4eb333a1e65e791a3f8b998cf3b11ae094adb2570f31f769a44", - "sha256:4b475bbd528acce094c503a3d2dbc2d05a4075f6d0ef7d9e7514518e14cc5191" + "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b", + "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539" ], "index": "pypi", - "version": "==18.6b4" + "version": "==19.10b0" + }, + "bleach": { + "hashes": [ + "sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16", + "sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa" + ], + "markers": "python_version != '3.0.*' and python_version >= '2.6' and python_version != '3.1.*'", + "version": "==3.1.0" }, "certifi": { "hashes": [ - "sha256:376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638", - "sha256:456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a" + "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", + "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" ], - "version": "==2018.8.24" + "version": "==2019.11.28" }, "chardet": { "hashes": [ @@ -76,66 +77,79 @@ }, "click": { "hashes": [ - "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", - "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" ], - "version": "==6.7" + "markers": "python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.1.*' and python_version >= '2.7'", + "version": "==7.0" }, "coverage": { "hashes": [ - "sha256:0dcf381f51f589f1f797449602a7fe4e63be8a7963c259c13742af3f30be902e", - "sha256:11a4bb30306def2fa012e3429de44a93ef2ae3b6ad3f6b800f6c578658a5c402", - "sha256:166c957a38b034050a14201f64eec11fc95e17bf2ba31fc07d887db82bae1a47", - "sha256:184e6680f85fcc1b371f67ab732290ecf96a225448198e14ec170986db47b0aa", - "sha256:1904deb72c561a8e445feb190db07ca4b165ee85567894b4b85fdb9bf21a27c0", - "sha256:1f2003b83426cfaadebff8b9bb1fb3650134a15fda3a81434cc8415896d7a7bc", - "sha256:1f462997b1804f8b5d1ee2b262626fc76b746e66023eb64f529af35991167c7c", - "sha256:213697f49eba45b5fb05e77f63bdb7c0d13eed12dcd08e6af43224615b28b524", - "sha256:2557da232b0daeb55afe2f7e55f7b80c56bfa2981864c6638b32b5691da9f4c3", - "sha256:395a8525f1456439a5d6c248bc1397040491047e3e0e0c4ceb2059155419cd3b", - "sha256:43d6334b35e50e74d034ec075ffd9082c559bca624924af6c7e9d2b8aef0f362", - "sha256:4566c74bde36aaaef0372fb11678edf43dcc73f4eb8dbb6987250658c4a3b95a", - "sha256:6d39cc527c9c7a30f20bed14b5cf9a7e87ef1f3528c1847d1c81caf75a31ebb6", - "sha256:8bd69d3cba21d885df6fe8728cee779a722da08cf84072558956c148b5ab61e5", - "sha256:a1d0fcbbe0735eb66c6622266b12e60ea8d37ada405cb8f73b154c5eec467187", - "sha256:ab706bfbb365f232be01a536a9199ee6bfc80c9b63fb7825fdd5f4ae5cc2a12c", - "sha256:afbf4cee68d2f2968b06951cf16c0b18513eb59bb3af0685084de6cacb04e217", - "sha256:bbc8913cd5889df7eab597a4b4074a2c6c5ee6ca9aad58a9ba0f3f847b1a99df", - "sha256:bd5428ab378a7432e43afa52b6bb9c5d48f5029f395a97dc9ebf87fc0f2a9d8b", - "sha256:c3efe0185583443e04f8519818f4772d92fbbdf5f9fa23165f2f2482b20efc37", - "sha256:d40277e918da575d008e2955a0ca6600f870bdb3570b07ee3a754ea9301862e7", - "sha256:d4b6ec6951e20ea3f5d1fefe35b4bcbf692d4306f1b932c28dd2ee4cb167152c", - "sha256:d5837e813ad62c856bc80f988c4e24e0d2b7b22a8a1dad8c1cfcb8ff4d4750a8", - "sha256:d9583ae0e152c5fb0142cb55c3a11e1b13006c00d0c3e8b35ccc2d4ebfc6645e", - "sha256:e27380cbe4088a1df514e75aa4fe6dc9e98bbd7902cf28ab16e8b2de0f8cb344", - "sha256:e624daef32f8808296312e72190c7e576852cb75c27935b31c1bbbde14ab353c", - "sha256:ef4278e5ac1e47c731ec5e3e48351721e01d2eb4fefa9b97fcdba7495a82cfad" + "sha256:0cd13a6e98c37b510a2d34c8281d5e1a226aaf9b65b7d770ef03c63169965351", + "sha256:1a4b6b6a2a3a6612e6361130c2cc3dc4378d8c221752b96167ccbad94b47f3cd", + "sha256:2ee55e6dba516ddf6f484aa83ccabbb0adf45a18892204c23486938d12258cde", + "sha256:3be5338a2eb4ef03c57f20917e1d12a1fd10e3853fed060b6d6b677cb3745898", + "sha256:44b783b02db03c4777d8cf71bae19eadc171a6f2a96777d916b2c30a1eb3d070", + "sha256:475bf7c4252af0a56e1abba9606f1e54127cdf122063095c75ab04f6f99cf45e", + "sha256:47c81ee687eafc2f1db7f03fbe99aab81330565ebc62fb3b61edfc2216a550c8", + "sha256:4a7f8e72b18f2aca288ff02255ce32cc830bc04d993efbc87abf6beddc9e56c0", + "sha256:50197163a22fd17f79086e087a787883b3ec9280a509807daf158dfc2a7ded02", + "sha256:56b13000acf891f700f5067512b804d1ec8c301d627486c678b903859d07f798", + "sha256:79388ae29c896299b3567965dbcd93255f175c17c6c7bca38614d12718c47466", + "sha256:79fd5d3d62238c4f583b75d48d53cdae759fe04d4fb18fe8b371d88ad2b6f8be", + "sha256:7fe3e2fde2bf1d7ce25ebcd2d3de3650b8d60d9a73ce6dcef36e20191291613d", + "sha256:81042a24f67b96e4287774014fa27220d8a4d91af1043389e4d73892efc89ac6", + "sha256:81326f1095c53111f8afc95da281e1414185f4a538609a77ca50bdfa39a6c207", + "sha256:8873dc0d8f42142ea9f20c27bbdc485190fff93823c6795be661703369e5877d", + "sha256:88d2cbcb0a112f47eef71eb95460b6995da18e6f8ca50c264585abc2c473154b", + "sha256:91f2491aeab9599956c45a77c5666d323efdec790bfe23fcceafcd91105d585a", + "sha256:979daa8655ae5a51e8e7a24e7d34e250ae8309fd9719490df92cbb2fe2b0422b", + "sha256:9c871b006c878a890c6e44a5b2f3c6291335324b298c904dc0402ee92ee1f0be", + "sha256:a6d092545e5af53e960465f652e00efbf5357adad177b2630d63978d85e46a72", + "sha256:b5ed7837b923d1d71c4f587ae1539ccd96bfd6be9788f507dbe94dab5febbb5d", + "sha256:ba259f68250f16d2444cbbfaddaa0bb20e1560a4fdaad50bece25c199e6af864", + "sha256:be1d89614c6b6c36d7578496dc8625123bda2ff44f224cf8b1c45b810ee7383f", + "sha256:c1b030a79749aa8d1f1486885040114ee56933b15ccfc90049ba266e4aa2139f", + "sha256:c95bb147fab76f2ecde332d972d8f4138b8f2daee6c466af4ff3b4f29bd4c19e", + "sha256:d52c1c2d7e856cecc05aa0526453cb14574f821b7f413cc279b9514750d795c1", + "sha256:d609a6d564ad3d327e9509846c2c47f170456344521462b469e5cb39e48ba31c", + "sha256:e1bad043c12fb58e8c7d92b3d7f2f49977dcb80a08a6d1e7a5114a11bf819fca", + "sha256:e5a675f6829c53c87d79117a8eb656cc4a5f8918185a32fc93ba09778e90f6db", + "sha256:fec32646b98baf4a22fdceb08703965bd16dea09051fbeb31a04b5b6e72b846c" ], "index": "pypi", - "version": "==5.0a2" + "version": "==5.0" }, "docutils": { "hashes": [ - "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6", - "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274", - "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6" + "sha256:7a6228589435302e421f5c473ce0180878b90f70227f7174cacde5efbd34275f", + "sha256:f1bad547016f945f7b35b28d8bead307821822ca3f8d4f87a1bd2ad1a8faab51" ], - "version": "==0.14" + "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.4.*' and python_version != '3.2.*'", + "version": "==0.16b0.dev0" + }, + "filelock": { + "hashes": [ + "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", + "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" + ], + "version": "==3.0.12" }, "flake8": { "hashes": [ - "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0", - "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37" + "sha256:6a35f5b8761f45c5513e3405f110a86bea57982c3b75b766ce7b65217abe1670", + "sha256:c01f8a3963b3571a8e6bd7a4063359aff90749e160778e03817cd9b71c9e07d2" ], "index": "pypi", - "version": "==3.5.0" + "version": "==3.6.0" }, "idna": { "hashes": [ - "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", - "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16" + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" ], - "version": "==2.7" + "markers": "python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.1.*' and python_version >= '2.7'", + "version": "==2.8" }, "imagesize": { "hashes": [ @@ -145,18 +159,63 @@ "markers": "python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'", "version": "==1.1.0" }, + "importlib-metadata": { + "hashes": [ + "sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45", + "sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f" + ], + "markers": "python_version < '3.8'", + "version": "==1.3.0" + }, "jinja2": { "hashes": [ - "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", - "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" + "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", + "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de" ], - "version": "==2.10" + "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*'", + "version": "==2.10.3" }, - "markupsafe": { + "keyring": { "hashes": [ - "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" + "sha256:a3f71fc0cf6b74e201e70532879ba1d15db25cb2c7407dce52fe52a6d5fc7b66", + "sha256:fc9cadedae35b77141f670f84c10a657147d2e526348698c93dd77f039979729" ], - "version": "==1.0" + "markers": "python_version >= '3.5'", + "version": "==20.0.0" + }, + "markupsafe": { + "hashes": [ + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" + ], + "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*'", + "version": "==1.1.1" }, "mccabe": { "hashes": [ @@ -165,206 +224,314 @@ ], "version": "==0.6.1" }, - "mock": { - "hashes": [ - "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1", - "sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba" - ], - "index": "pypi", - "version": "==2.0.0" - }, "more-itertools": { "hashes": [ - "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092", - "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e", - "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d" + "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d", + "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564" ], "index": "pypi", - "version": "==4.3.0" + "version": "==8.0.2" }, "packaging": { "hashes": [ - "sha256:e9215d2d2535d3ae866c3d6efc77d5b24a0192cce0ff20e42896cc0664f889c0", - "sha256:f019b770dd64e585a99714f1fd5e01c7a8f11b45635aa953fd41c689a657375b" + "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", + "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" ], - "version": "==17.1" + "markers": "python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.6'", + "version": "==19.2" }, - "pbr": { + "pathspec": { "hashes": [ - "sha256:1b8be50d938c9bb75d0eaf7eda111eec1bf6dc88a62a6412e33bf077457e0f45", - "sha256:b486975c0cafb6beeb50ca0e17ba047647f229087bd74e37f4a7e2cac17d2caa" + "sha256:e285ccc8b0785beadd4c18e5708b12bb8fcf529a1e61215b3feff1d1e559ea5c" ], - "version": "==4.2.0" + "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*'", + "version": "==0.6.0" }, "pkginfo": { "hashes": [ - "sha256:5878d542a4b3f237e359926384f1dde4e099c9f5525d236b1840cf704fa8d474", - "sha256:a39076cb3eb34c333a0dd390b568e9e1e881c7bf2cc0aee12120636816f55aee" + "sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb", + "sha256:a6d9e40ca61ad3ebd0b72fbadd4fba16e4c0e4df0428c041e01e06eb6ee71f32" ], - "version": "==1.4.2" + "version": "==1.5.0.1" }, "pluggy": { "hashes": [ - "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", - "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1" + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" ], - "markers": "python_version != '3.3.*' and python_version != '3.2.*' and python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.0.*'", - "version": "==0.7.1" + "markers": "python_version >= '3.5'", + "version": "==0.13.1" }, "py": { "hashes": [ - "sha256:06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1", - "sha256:50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6" + "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", + "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" ], - "markers": "python_version != '3.3.*' and python_version != '3.2.*' and python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.0.*'", - "version": "==1.6.0" + "markers": "python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7'", + "version": "==1.8.0" }, "pycodestyle": { "hashes": [ - "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766", - "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9" + "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83", + "sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a" ], - "version": "==2.3.1" + "version": "==2.4.0" }, "pyflakes": { "hashes": [ - "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f", - "sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805" + "sha256:9a7662ec724d0120012f6e29d6248ae3727d821bba522a0e6b356eff19126a49", + "sha256:f661252913bc1dbe7fcfcbf0af0db3f42ab65aabd1a6ca68fe5d466bace94dae" ], - "version": "==1.6.0" + "markers": "python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.1.*' and python_version >= '2.7'", + "version": "==2.0.0" }, "pygments": { "hashes": [ - "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", - "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" + "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", + "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe" ], - "version": "==2.2.0" + "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.4.*' and python_version != '3.2.*'", + "version": "==2.5.2" }, "pyparsing": { "hashes": [ - "sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04", - "sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010" + "sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", + "sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a" ], - "version": "==2.2.0" + "markers": "python_version != '3.1.*' and python_version >= '2.6' and python_version != '3.0.*' and python_version != '3.2.*'", + "version": "==2.4.5" }, "pytest": { "hashes": [ - "sha256:453cbbbe5ce6db38717d282b758b917de84802af4288910c12442984bde7b823", - "sha256:a8a07f84e680482eb51e244370aaf2caa6301ef265f37c2bdefb3dd3b663f99d" + "sha256:6b571215b5a790f9b41f19f3531c53a45cf6bb8ef2988bc1ff9afb38270b25fa", + "sha256:e41d489ff43948babd0fad7ad5e49b8735d5d55e26628a58673c39ff61d95de4" ], "index": "pypi", - "version": "==3.8.0" + "version": "==5.3.2" }, "pytz": { "hashes": [ - "sha256:a061aa0a9e06881eb8b3b2b43f05b9439d6583c206d0a6c340ff72a7b6669053", - "sha256:ffb9ef1de172603304d9d2819af6f5ece76f2e85ec10692a524dd876e72bf277" + "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", + "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" ], - "version": "==2018.5" + "version": "==2019.3" }, "pyyaml": { "hashes": [ - "sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb", - "sha256:3108529b78577327d15eec243f0ff348a0640b0c3478d67ad7f5648f93bac3e2", - "sha256:3c17fb92c8ba2f525e4b5f7941d850e7a48c3a59b32d331e2502a3cdc6648e76", - "sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b", - "sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b" + "sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc", + "sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803", + "sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc", + "sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15", + "sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075", + "sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd", + "sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31", + "sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f", + "sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c", + "sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04", + "sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4" ], "index": "pypi", - "version": "==4.2b4" + "version": "==5.2" + }, + "readme-renderer": { + "hashes": [ + "sha256:bb16f55b259f27f75f640acf5e00cf897845a8b3e4731b5c1a436e4b8529202f", + "sha256:c8532b79afc0375a85f10433eca157d6b50f7d6990f337fa498c96cd4bfc203d" + ], + "version": "==24.0" + }, + "regex": { + "hashes": [ + "sha256:3dbd8333fd2ebd50977ac8747385a73aa1f546eb6b16fcd83d274470fe11f243", + "sha256:40b7d1291a56897927e08bb973f8c186c2feb14c7f708bfe7aaee09483e85a20", + "sha256:719978a9145d59fc78509ea1d1bb74243f93583ef2a34dcc5623cf8118ae9726", + "sha256:75cf3796f89f75f83207a5c6a6e14eaf57e0369ef0ffff8e22bf36bbcfa0f1de", + "sha256:77396cf80be8b2a35db863cca4c1a902d88ceeb183adab328b81184e71a5eafe", + "sha256:77a3799152951d6d14ae5720ca162c97c64f85d4755da585418eac216b736cad", + "sha256:91235c98283d2bddf1a588f0fbc2da8afa37959294bbd18b76297bdf316ba4d6", + "sha256:aaffd68c4c1ed891366d5c390081f4bf6337595e76a157baf453603d8e53fbcb", + "sha256:ad9e3c7260809c0d1ded100269f78ea0217c0704f1eaaf40a382008461848b45", + "sha256:c203c9ee755e9656d0af8fab82754d5a664ebaf707b3f883c7eff6a3dd5151cf", + "sha256:e865bc508e316a3a09d36c8621596e6599a203bc54f1cd41020a127ccdac468a" + ], + "version": "==2019.12.9" }, "requests": { "hashes": [ - "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", - "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a" + "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", + "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" ], "index": "pypi", - "version": "==2.19.1" + "version": "==2.22.0" }, "requests-toolbelt": { "hashes": [ - "sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237", - "sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5" + "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f", + "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0" ], - "markers": "python_version != '3.3.*' and python_version != '3.2.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version >= '2.6' and python_version < '4'", - "version": "==0.8.0" + "markers": "python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7'", + "version": "==0.9.1" }, "six": { "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", + "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" ], - "version": "==1.11.0" + "markers": "python_version != '3.1.*' and python_version >= '2.6' and python_version != '3.0.*'", + "version": "==1.13.0" }, "snowballstemmer": { "hashes": [ - "sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128", - "sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89" + "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", + "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52" ], - "version": "==1.2.1" + "version": "==2.0.0" }, "sphinx": { "hashes": [ - "sha256:95acd6648902333647a0e0564abdb28a74b0a76d2333148aa35e5ed1f56d3c4b", - "sha256:c091dbdd5cc5aac6eb95d591a819fd18bccec90ffb048ec465b165a48b839b45" + "sha256:3b16e48e791a322d584489ab28d8800652123d1fbfdd173e2965a31d40bf22d7", + "sha256:559c1a8ed1365a982f77650720b41114414139a635692a23c2990824d0a84cf2" ], "index": "pypi", - "version": "==1.8.0" + "markers": "python_version < '4' and python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*'", + "version": "==2.2.2" }, - "sphinxcontrib-websupport": { + "sphinxcontrib-applehelp": { "hashes": [ - "sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd", - "sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9" + "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", + "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d" ], - "markers": "python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'", - "version": "==1.1.0" + "version": "==1.0.1" + }, + "sphinxcontrib-devhelp": { + "hashes": [ + "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", + "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981" + ], + "version": "==1.0.1" + }, + "sphinxcontrib-htmlhelp": { + "hashes": [ + "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422", + "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7" + ], + "version": "==1.0.2" + }, + "sphinxcontrib-jsmath": { + "hashes": [ + "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", + "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.1" + }, + "sphinxcontrib-qthelp": { + "hashes": [ + "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", + "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f" + ], + "version": "==1.0.2" + }, + "sphinxcontrib-serializinghtml": { + "hashes": [ + "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", + "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768" + ], + "version": "==1.1.3" }, "toml": { "hashes": [ - "sha256:380178cde50a6a79f9d2cf6f42a62a5174febe5eea4126fe4038785f1d888d42", - "sha256:a7901919d3e4f92ffba7ff40a9d697e35bbbc8a8049fe8da742f34c83606d957" + "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", + "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" ], - "version": "==0.9.6" + "version": "==0.10.0" }, "tox": { "hashes": [ - "sha256:433bb93c57edae263150767e672a0d468ab4fefcc1958eb4013e56a670bb851e", - "sha256:bfb4e4efb7c61a54bc010a5c00fdbe0973bc4bdf04090bfcd3c93c901006177c" + "sha256:7efd010a98339209f3a8292f02909b51c58417bfc6838ab7eca14cf90f96117a", + "sha256:8dd653bf0c6716a435df363c853cad1f037f9d5fddd0abc90d0f48ad06f39d03" ], "index": "pypi", - "version": "==3.3.0" + "version": "==3.14.2" }, "tqdm": { "hashes": [ - "sha256:18f1818ce951aeb9ea162ae1098b43f583f7d057b34d706f66939353d1208889", - "sha256:df02c0650160986bac0218bb07952245fc6960d23654648b5d5526ad5a4128c9" + "sha256:7543892c59720e36e4212180274d8f58dde36803bc1f6370fd09afa20b8f5892", + "sha256:f0ab01cf3ae5673d18f918700c0165e5fad0f26b5ebe4b34f62ead92686b5340" ], "markers": "python_version != '3.1.*' and python_version >= '2.6' and python_version != '3.0.*'", - "version": "==4.26.0" + "version": "==4.40.2" }, "twine": { "hashes": [ - "sha256:08eb132bbaec40c6d25b358f546ec1dc96ebd2638a86eea68769d9e67fe2b129", - "sha256:2fd9a4d9ff0bcacf41fdc40c8cb0cfaef1f1859457c9653fd1b92237cc4e9f25" + "sha256:c1af8ca391e43b0a06bbc155f7f67db0bf0d19d284bfc88d1675da497a946124", + "sha256:d561a5e511f70275e5a485a6275ff61851c16ffcb3a95a602189161112d9f160" ], "index": "pypi", - "version": "==1.11.0" + "version": "==3.1.1" + }, + "typed-ast": { + "hashes": [ + "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", + "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", + "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", + "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", + "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", + "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", + "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", + "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", + "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", + "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", + "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", + "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", + "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", + "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", + "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", + "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", + "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", + "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", + "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", + "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" + ], + "version": "==1.4.0" }, "urllib3": { "hashes": [ - "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", - "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5" + "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", + "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745" ], - "markers": "python_version != '3.3.*' and python_version != '3.2.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version >= '2.6' and python_version < '4'", - "version": "==1.23" + "markers": "python_version < '4' and python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*'", + "version": "==1.25.7" }, "virtualenv": { "hashes": [ - "sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669", - "sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752" + "sha256:116655188441670978117d0ebb6451eb6a7526f9ae0796cc0dee6bd7356909b0", + "sha256:b57776b44f91511866594e477dd10e76a6eb44439cdd7f06dcd30ba4c5bd854f" + ], + "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.2.*'", + "version": "==16.7.8" + }, + "wcwidth": { + "hashes": [ + "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", + "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + ], + "version": "==0.1.7" + }, + "webencodings": { + "hashes": [ + "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", + "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" + ], + "version": "==0.5.1" + }, + "zipp": { + "hashes": [ + "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", + "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" ], - "markers": "python_version != '3.2.*' and python_version != '3.1.*' and python_version >= '2.7' and python_version != '3.0.*'", - "version": "==16.0.0" + "markers": "python_version >= '3.4'", + "version": "==0.6.0" } } } diff --git a/tap/tests/test_adapter.py b/tap/tests/test_adapter.py index 168372b..1d89b87 100644 --- a/tap/tests/test_adapter.py +++ b/tap/tests/test_adapter.py @@ -1,9 +1,6 @@ # Copyright (c) 2019, Matt Layman and contributors -try: - from unittest import mock -except ImportError: - import mock +from unittest import mock from tap.adapter import Adapter from tap.tests import TestCase diff --git a/tap/tests/test_loader.py b/tap/tests/test_loader.py index e6e6199..13e536c 100644 --- a/tap/tests/test_loader.py +++ b/tap/tests/test_loader.py @@ -5,11 +5,7 @@ import os import tempfile import unittest - -try: - from unittest import mock -except ImportError: - import mock +from unittest import mock from tap.i18n import _ from tap.loader import Loader diff --git a/tap/tests/test_main.py b/tap/tests/test_main.py index 0587ead..01e72fe 100644 --- a/tap/tests/test_main.py +++ b/tap/tests/test_main.py @@ -2,11 +2,7 @@ import argparse import os - -try: - from unittest import mock -except ImportError: - import mock +from unittest import mock from tap.loader import Loader from tap.main import build_suite, get_status, main, parse_args diff --git a/tap/tests/test_parser.py b/tap/tests/test_parser.py index cf888a3..8e89ca0 100644 --- a/tap/tests/test_parser.py +++ b/tap/tests/test_parser.py @@ -6,11 +6,7 @@ import sys import tempfile import unittest - -try: - from unittest import mock -except ImportError: - import mock +from unittest import mock from tap.parser import Parser diff --git a/tap/tests/test_runner.py b/tap/tests/test_runner.py index faece59..507764e 100644 --- a/tap/tests/test_runner.py +++ b/tap/tests/test_runner.py @@ -4,11 +4,7 @@ import sys import tempfile import unittest - -try: - from unittest import mock -except ImportError: - import mock +from unittest import mock from tap import TAPTestRunner from tap.runner import TAPTestResult, _tracker diff --git a/tap/tests/test_tracker.py b/tap/tests/test_tracker.py index 592bd9c..447fe1d 100644 --- a/tap/tests/test_tracker.py +++ b/tap/tests/test_tracker.py @@ -1,18 +1,10 @@ # Copyright (c) 2019, Matt Layman and contributors import inspect +from io import StringIO import os import tempfile - -try: - from cStringIO import StringIO -except ImportError: - from io import StringIO - -try: - from unittest import mock -except ImportError: - import mock +from unittest import mock from tap.i18n import _ from tap.tests import TestCase diff --git a/tox.ini b/tox.ini index 903f1b9..2884586 100644 --- a/tox.ini +++ b/tox.ini @@ -10,27 +10,22 @@ envlist = [testenv] deps = - mock pytest commands = pytest {envsitepackagesdir}/tap [testenv:windows] basepython = python3.6 deps = - mock pytest commands = pytest [testenv:with_optional] deps = - mock pyyaml more-itertools commands = python tap/tests/run.py [testenv:runner] -deps = - mock commands = python tap/tests/run.py [testenv:lint] @@ -43,7 +38,6 @@ commands = [testenv:integration] deps = - mock pytest pytest-tap commands = @@ -56,7 +50,6 @@ setenv = passenv = TRAVIS* deps = coverage - mock codecov pyyaml more-itertools From f3c900759ae8f34c6485e1a2df79bdbfd352d60b Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Sat, 14 Dec 2019 20:24:50 -0500 Subject: [PATCH 32/43] Remove Pipenv usage. (#100) This library is so small that it's easier to create a virtualenv. --- MANIFEST.in | 2 -- docs/contributing.rst | 13 ++++--------- docs/releases.rst | 1 + requirements-dev.txt | 13 +++++++++++++ 4 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 requirements-dev.txt diff --git a/MANIFEST.in b/MANIFEST.in index 8a23171..56c4599 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,8 +1,6 @@ include AUTHORS include LICENSE include README.md -include Pipfile -include Pipfile.lock recursive-include docs * recursive-include tap/locale * prune docs/_build diff --git a/docs/contributing.rst b/docs/contributing.rst index 3a7d174..e0c47f8 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -14,20 +14,15 @@ when you're ready. Setup ----- -tappy uses Pipenv -to manage development. -The following instructions assume that Pipenv is installed. -See the `Pipenv install instructions `_ -for more details. - -After installing Pipenv: +tappy uses the built-in `venv` module. .. code-block:: console $ git clone git@github.com:python-tap/tappy.git $ cd tappy - $ pipenv install --dev --ignore-pipfile - $ pipenv shell + $ python3 -m venv venv + $ source venv/bin/activate + $ pip install -r requirements-dev.txt $ # Edit some files and run the tests. $ pytest diff --git a/docs/releases.rst b/docs/releases.rst index ee84c7e..01be5be 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -6,6 +6,7 @@ Version 3.0, To Be Released * Drop support for Python 2 (it is end-of-life). * Add support for subtests. +* Discontinue use of Pipenv for managing development. Version 2.6.2, Released October 20, 2019 ---------------------------------------- diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..aebdd34 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,13 @@ +Babel +black +coverage +flake8 +pytest +requests +Sphinx +tox +twine + +# These are the optional dependencies to enable TAP version 13 support. +more-itertools +pyyaml From cf652dc78f94e37e46308db28938ce90c9e614fb Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Sat, 14 Dec 2019 20:25:19 -0500 Subject: [PATCH 33/43] Remove the Pipfile and Pipfile.lock. --- Pipfile | 19 -- Pipfile.lock | 537 --------------------------------------------------- 2 files changed, 556 deletions(-) delete mode 100644 Pipfile delete mode 100644 Pipfile.lock diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 153fe19..0000000 --- a/Pipfile +++ /dev/null @@ -1,19 +0,0 @@ -[[source]] -url = "https://pypi.python.org/simple" -verify_ssl = true -name = "pypi" - -[dev-packages] -Babel = "*" -coverage = "*" -"flake8" = "==3.6.0" -requests = "*" -Sphinx = "*" -tox = "*" -twine = "*" -pytest = "*" -more-itertools = "*" -pyyaml = "*" -black = "*" - -[packages] diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 65fd892..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,537 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "0849fd48a1c241dba3236f10ab68ec946acd2bd1e48a0500debc97d83de7131f" - }, - "pipfile-spec": 6, - "requires": {}, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": {}, - "develop": { - "alabaster": { - "hashes": [ - "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", - "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02" - ], - "version": "==0.7.12" - }, - "appdirs": { - "hashes": [ - "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", - "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" - ], - "version": "==1.4.3" - }, - "attrs": { - "hashes": [ - "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", - "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" - ], - "markers": "python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7'", - "version": "==19.3.0" - }, - "babel": { - "hashes": [ - "sha256:af92e6106cb7c55286b25b38ad7695f8b4efb36a90ba483d7f7a6628c46158ab", - "sha256:e86135ae101e31e2c8ec20a4e0c5220f4eed12487d5cf3f78be7e98d3a57fc28" - ], - "index": "pypi", - "version": "==2.7.0" - }, - "black": { - "hashes": [ - "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b", - "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539" - ], - "index": "pypi", - "version": "==19.10b0" - }, - "bleach": { - "hashes": [ - "sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16", - "sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa" - ], - "markers": "python_version != '3.0.*' and python_version >= '2.6' and python_version != '3.1.*'", - "version": "==3.1.0" - }, - "certifi": { - "hashes": [ - "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", - "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" - ], - "version": "==2019.11.28" - }, - "chardet": { - "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" - ], - "version": "==3.0.4" - }, - "click": { - "hashes": [ - "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", - "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" - ], - "markers": "python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.1.*' and python_version >= '2.7'", - "version": "==7.0" - }, - "coverage": { - "hashes": [ - "sha256:0cd13a6e98c37b510a2d34c8281d5e1a226aaf9b65b7d770ef03c63169965351", - "sha256:1a4b6b6a2a3a6612e6361130c2cc3dc4378d8c221752b96167ccbad94b47f3cd", - "sha256:2ee55e6dba516ddf6f484aa83ccabbb0adf45a18892204c23486938d12258cde", - "sha256:3be5338a2eb4ef03c57f20917e1d12a1fd10e3853fed060b6d6b677cb3745898", - "sha256:44b783b02db03c4777d8cf71bae19eadc171a6f2a96777d916b2c30a1eb3d070", - "sha256:475bf7c4252af0a56e1abba9606f1e54127cdf122063095c75ab04f6f99cf45e", - "sha256:47c81ee687eafc2f1db7f03fbe99aab81330565ebc62fb3b61edfc2216a550c8", - "sha256:4a7f8e72b18f2aca288ff02255ce32cc830bc04d993efbc87abf6beddc9e56c0", - "sha256:50197163a22fd17f79086e087a787883b3ec9280a509807daf158dfc2a7ded02", - "sha256:56b13000acf891f700f5067512b804d1ec8c301d627486c678b903859d07f798", - "sha256:79388ae29c896299b3567965dbcd93255f175c17c6c7bca38614d12718c47466", - "sha256:79fd5d3d62238c4f583b75d48d53cdae759fe04d4fb18fe8b371d88ad2b6f8be", - "sha256:7fe3e2fde2bf1d7ce25ebcd2d3de3650b8d60d9a73ce6dcef36e20191291613d", - "sha256:81042a24f67b96e4287774014fa27220d8a4d91af1043389e4d73892efc89ac6", - "sha256:81326f1095c53111f8afc95da281e1414185f4a538609a77ca50bdfa39a6c207", - "sha256:8873dc0d8f42142ea9f20c27bbdc485190fff93823c6795be661703369e5877d", - "sha256:88d2cbcb0a112f47eef71eb95460b6995da18e6f8ca50c264585abc2c473154b", - "sha256:91f2491aeab9599956c45a77c5666d323efdec790bfe23fcceafcd91105d585a", - "sha256:979daa8655ae5a51e8e7a24e7d34e250ae8309fd9719490df92cbb2fe2b0422b", - "sha256:9c871b006c878a890c6e44a5b2f3c6291335324b298c904dc0402ee92ee1f0be", - "sha256:a6d092545e5af53e960465f652e00efbf5357adad177b2630d63978d85e46a72", - "sha256:b5ed7837b923d1d71c4f587ae1539ccd96bfd6be9788f507dbe94dab5febbb5d", - "sha256:ba259f68250f16d2444cbbfaddaa0bb20e1560a4fdaad50bece25c199e6af864", - "sha256:be1d89614c6b6c36d7578496dc8625123bda2ff44f224cf8b1c45b810ee7383f", - "sha256:c1b030a79749aa8d1f1486885040114ee56933b15ccfc90049ba266e4aa2139f", - "sha256:c95bb147fab76f2ecde332d972d8f4138b8f2daee6c466af4ff3b4f29bd4c19e", - "sha256:d52c1c2d7e856cecc05aa0526453cb14574f821b7f413cc279b9514750d795c1", - "sha256:d609a6d564ad3d327e9509846c2c47f170456344521462b469e5cb39e48ba31c", - "sha256:e1bad043c12fb58e8c7d92b3d7f2f49977dcb80a08a6d1e7a5114a11bf819fca", - "sha256:e5a675f6829c53c87d79117a8eb656cc4a5f8918185a32fc93ba09778e90f6db", - "sha256:fec32646b98baf4a22fdceb08703965bd16dea09051fbeb31a04b5b6e72b846c" - ], - "index": "pypi", - "version": "==5.0" - }, - "docutils": { - "hashes": [ - "sha256:7a6228589435302e421f5c473ce0180878b90f70227f7174cacde5efbd34275f", - "sha256:f1bad547016f945f7b35b28d8bead307821822ca3f8d4f87a1bd2ad1a8faab51" - ], - "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.4.*' and python_version != '3.2.*'", - "version": "==0.16b0.dev0" - }, - "filelock": { - "hashes": [ - "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", - "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" - ], - "version": "==3.0.12" - }, - "flake8": { - "hashes": [ - "sha256:6a35f5b8761f45c5513e3405f110a86bea57982c3b75b766ce7b65217abe1670", - "sha256:c01f8a3963b3571a8e6bd7a4063359aff90749e160778e03817cd9b71c9e07d2" - ], - "index": "pypi", - "version": "==3.6.0" - }, - "idna": { - "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" - ], - "markers": "python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.1.*' and python_version >= '2.7'", - "version": "==2.8" - }, - "imagesize": { - "hashes": [ - "sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8", - "sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5" - ], - "markers": "python_version >= '2.7' and python_version != '3.3.*' and python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.2.*'", - "version": "==1.1.0" - }, - "importlib-metadata": { - "hashes": [ - "sha256:073a852570f92da5f744a3472af1b61e28e9f78ccf0c9117658dc32b15de7b45", - "sha256:d95141fbfa7ef2ec65cfd945e2af7e5a6ddbd7c8d9a25e66ff3be8e3daf9f60f" - ], - "markers": "python_version < '3.8'", - "version": "==1.3.0" - }, - "jinja2": { - "hashes": [ - "sha256:74320bb91f31270f9551d46522e33af46a80c3d619f4a4bf42b3164d30b5911f", - "sha256:9fe95f19286cfefaa917656583d020be14e7859c6b0252588391e47db34527de" - ], - "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*'", - "version": "==2.10.3" - }, - "keyring": { - "hashes": [ - "sha256:a3f71fc0cf6b74e201e70532879ba1d15db25cb2c7407dce52fe52a6d5fc7b66", - "sha256:fc9cadedae35b77141f670f84c10a657147d2e526348698c93dd77f039979729" - ], - "markers": "python_version >= '3.5'", - "version": "==20.0.0" - }, - "markupsafe": { - "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" - ], - "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*'", - "version": "==1.1.1" - }, - "mccabe": { - "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" - ], - "version": "==0.6.1" - }, - "more-itertools": { - "hashes": [ - "sha256:b84b238cce0d9adad5ed87e745778d20a3f8487d0f0cb8b8a586816c7496458d", - "sha256:c833ef592a0324bcc6a60e48440da07645063c453880c9477ceb22490aec1564" - ], - "index": "pypi", - "version": "==8.0.2" - }, - "packaging": { - "hashes": [ - "sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", - "sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" - ], - "markers": "python_version != '3.1.*' and python_version != '3.2.*' and python_version != '3.0.*' and python_version >= '2.6'", - "version": "==19.2" - }, - "pathspec": { - "hashes": [ - "sha256:e285ccc8b0785beadd4c18e5708b12bb8fcf529a1e61215b3feff1d1e559ea5c" - ], - "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*'", - "version": "==0.6.0" - }, - "pkginfo": { - "hashes": [ - "sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb", - "sha256:a6d9e40ca61ad3ebd0b72fbadd4fba16e4c0e4df0428c041e01e06eb6ee71f32" - ], - "version": "==1.5.0.1" - }, - "pluggy": { - "hashes": [ - "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", - "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" - ], - "markers": "python_version >= '3.5'", - "version": "==0.13.1" - }, - "py": { - "hashes": [ - "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", - "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" - ], - "markers": "python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7'", - "version": "==1.8.0" - }, - "pycodestyle": { - "hashes": [ - "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83", - "sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a" - ], - "version": "==2.4.0" - }, - "pyflakes": { - "hashes": [ - "sha256:9a7662ec724d0120012f6e29d6248ae3727d821bba522a0e6b356eff19126a49", - "sha256:f661252913bc1dbe7fcfcbf0af0db3f42ab65aabd1a6ca68fe5d466bace94dae" - ], - "markers": "python_version != '3.0.*' and python_version != '3.2.*' and python_version != '3.3.*' and python_version != '3.1.*' and python_version >= '2.7'", - "version": "==2.0.0" - }, - "pygments": { - "hashes": [ - "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b", - "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe" - ], - "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.4.*' and python_version != '3.2.*'", - "version": "==2.5.2" - }, - "pyparsing": { - "hashes": [ - "sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", - "sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a" - ], - "markers": "python_version != '3.1.*' and python_version >= '2.6' and python_version != '3.0.*' and python_version != '3.2.*'", - "version": "==2.4.5" - }, - "pytest": { - "hashes": [ - "sha256:6b571215b5a790f9b41f19f3531c53a45cf6bb8ef2988bc1ff9afb38270b25fa", - "sha256:e41d489ff43948babd0fad7ad5e49b8735d5d55e26628a58673c39ff61d95de4" - ], - "index": "pypi", - "version": "==5.3.2" - }, - "pytz": { - "hashes": [ - "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d", - "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be" - ], - "version": "==2019.3" - }, - "pyyaml": { - "hashes": [ - "sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc", - "sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803", - "sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc", - "sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15", - "sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075", - "sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd", - "sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31", - "sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f", - "sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c", - "sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04", - "sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4" - ], - "index": "pypi", - "version": "==5.2" - }, - "readme-renderer": { - "hashes": [ - "sha256:bb16f55b259f27f75f640acf5e00cf897845a8b3e4731b5c1a436e4b8529202f", - "sha256:c8532b79afc0375a85f10433eca157d6b50f7d6990f337fa498c96cd4bfc203d" - ], - "version": "==24.0" - }, - "regex": { - "hashes": [ - "sha256:3dbd8333fd2ebd50977ac8747385a73aa1f546eb6b16fcd83d274470fe11f243", - "sha256:40b7d1291a56897927e08bb973f8c186c2feb14c7f708bfe7aaee09483e85a20", - "sha256:719978a9145d59fc78509ea1d1bb74243f93583ef2a34dcc5623cf8118ae9726", - "sha256:75cf3796f89f75f83207a5c6a6e14eaf57e0369ef0ffff8e22bf36bbcfa0f1de", - "sha256:77396cf80be8b2a35db863cca4c1a902d88ceeb183adab328b81184e71a5eafe", - "sha256:77a3799152951d6d14ae5720ca162c97c64f85d4755da585418eac216b736cad", - "sha256:91235c98283d2bddf1a588f0fbc2da8afa37959294bbd18b76297bdf316ba4d6", - "sha256:aaffd68c4c1ed891366d5c390081f4bf6337595e76a157baf453603d8e53fbcb", - "sha256:ad9e3c7260809c0d1ded100269f78ea0217c0704f1eaaf40a382008461848b45", - "sha256:c203c9ee755e9656d0af8fab82754d5a664ebaf707b3f883c7eff6a3dd5151cf", - "sha256:e865bc508e316a3a09d36c8621596e6599a203bc54f1cd41020a127ccdac468a" - ], - "version": "==2019.12.9" - }, - "requests": { - "hashes": [ - "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", - "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" - ], - "index": "pypi", - "version": "==2.22.0" - }, - "requests-toolbelt": { - "hashes": [ - "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f", - "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0" - ], - "markers": "python_version != '3.1.*' and python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.2.*' and python_version >= '2.7'", - "version": "==0.9.1" - }, - "six": { - "hashes": [ - "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", - "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" - ], - "markers": "python_version != '3.1.*' and python_version >= '2.6' and python_version != '3.0.*'", - "version": "==1.13.0" - }, - "snowballstemmer": { - "hashes": [ - "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", - "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52" - ], - "version": "==2.0.0" - }, - "sphinx": { - "hashes": [ - "sha256:3b16e48e791a322d584489ab28d8800652123d1fbfdd173e2965a31d40bf22d7", - "sha256:559c1a8ed1365a982f77650720b41114414139a635692a23c2990824d0a84cf2" - ], - "index": "pypi", - "markers": "python_version < '4' and python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*'", - "version": "==2.2.2" - }, - "sphinxcontrib-applehelp": { - "hashes": [ - "sha256:edaa0ab2b2bc74403149cb0209d6775c96de797dfd5b5e2a71981309efab3897", - "sha256:fb8dee85af95e5c30c91f10e7eb3c8967308518e0f7488a2828ef7bc191d0d5d" - ], - "version": "==1.0.1" - }, - "sphinxcontrib-devhelp": { - "hashes": [ - "sha256:6c64b077937330a9128a4da74586e8c2130262f014689b4b89e2d08ee7294a34", - "sha256:9512ecb00a2b0821a146736b39f7aeb90759834b07e81e8cc23a9c70bacb9981" - ], - "version": "==1.0.1" - }, - "sphinxcontrib-htmlhelp": { - "hashes": [ - "sha256:4670f99f8951bd78cd4ad2ab962f798f5618b17675c35c5ac3b2132a14ea8422", - "sha256:d4fd39a65a625c9df86d7fa8a2d9f3cd8299a3a4b15db63b50aac9e161d8eff7" - ], - "version": "==1.0.2" - }, - "sphinxcontrib-jsmath": { - "hashes": [ - "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", - "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.1" - }, - "sphinxcontrib-qthelp": { - "hashes": [ - "sha256:513049b93031beb1f57d4daea74068a4feb77aa5630f856fcff2e50de14e9a20", - "sha256:79465ce11ae5694ff165becda529a600c754f4bc459778778c7017374d4d406f" - ], - "version": "==1.0.2" - }, - "sphinxcontrib-serializinghtml": { - "hashes": [ - "sha256:c0efb33f8052c04fd7a26c0a07f1678e8512e0faec19f4aa8f2473a8b81d5227", - "sha256:db6615af393650bf1151a6cd39120c29abaf93cc60db8c48eb2dddbfdc3a9768" - ], - "version": "==1.1.3" - }, - "toml": { - "hashes": [ - "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", - "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" - ], - "version": "==0.10.0" - }, - "tox": { - "hashes": [ - "sha256:7efd010a98339209f3a8292f02909b51c58417bfc6838ab7eca14cf90f96117a", - "sha256:8dd653bf0c6716a435df363c853cad1f037f9d5fddd0abc90d0f48ad06f39d03" - ], - "index": "pypi", - "version": "==3.14.2" - }, - "tqdm": { - "hashes": [ - "sha256:7543892c59720e36e4212180274d8f58dde36803bc1f6370fd09afa20b8f5892", - "sha256:f0ab01cf3ae5673d18f918700c0165e5fad0f26b5ebe4b34f62ead92686b5340" - ], - "markers": "python_version != '3.1.*' and python_version >= '2.6' and python_version != '3.0.*'", - "version": "==4.40.2" - }, - "twine": { - "hashes": [ - "sha256:c1af8ca391e43b0a06bbc155f7f67db0bf0d19d284bfc88d1675da497a946124", - "sha256:d561a5e511f70275e5a485a6275ff61851c16ffcb3a95a602189161112d9f160" - ], - "index": "pypi", - "version": "==3.1.1" - }, - "typed-ast": { - "hashes": [ - "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", - "sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", - "sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", - "sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", - "sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", - "sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", - "sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", - "sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", - "sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", - "sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", - "sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", - "sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", - "sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", - "sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", - "sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", - "sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", - "sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", - "sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", - "sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", - "sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" - ], - "version": "==1.4.0" - }, - "urllib3": { - "hashes": [ - "sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", - "sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745" - ], - "markers": "python_version < '4' and python_version != '3.1.*' and python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.2.*'", - "version": "==1.25.7" - }, - "virtualenv": { - "hashes": [ - "sha256:116655188441670978117d0ebb6451eb6a7526f9ae0796cc0dee6bd7356909b0", - "sha256:b57776b44f91511866594e477dd10e76a6eb44439cdd7f06dcd30ba4c5bd854f" - ], - "markers": "python_version != '3.1.*' and python_version != '3.3.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.2.*'", - "version": "==16.7.8" - }, - "wcwidth": { - "hashes": [ - "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", - "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" - ], - "version": "==0.1.7" - }, - "webencodings": { - "hashes": [ - "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", - "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" - ], - "version": "==0.5.1" - }, - "zipp": { - "hashes": [ - "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", - "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" - ], - "markers": "python_version >= '3.4'", - "version": "==0.6.0" - } - } -} From 3c4107a49ac568cff9d6b284664520c272401607 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Sat, 14 Dec 2019 21:05:13 -0500 Subject: [PATCH 34/43] Add support for running like `python -m tap`. (#101) * Add support for running like ``python -m tap``. Fixes #84 * Test that the module runner works. * Test the main_module function. --- .travis.yml | 3 +++ AUTHORS | 1 + docs/index.rst | 12 ++++++++++++ docs/producers.rst | 2 ++ docs/releases.rst | 1 + tap/__main__.py | 3 +++ tap/main.py | 8 ++++++++ tap/tests/test_main.py | 10 +++++++++- tox.ini | 4 ++++ 9 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 tap/__main__.py diff --git a/.travis.yml b/.travis.yml index 2eab2ca..2d6972d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,6 +25,9 @@ matrix: - os: linux python: 3.7 env: TOX_ENV=runner + - os: linux + python: 3.7 + env: TOX_ENV=module - os: linux python: 3.7 env: TOX_ENV=lint diff --git a/AUTHORS b/AUTHORS index 06d46c5..c472381 100644 --- a/AUTHORS +++ b/AUTHORS @@ -13,3 +13,4 @@ Contributors * Michael F. Lamb (http://datagrok.org) * Nicolas Caniart * Richard Bosworth +* Ross Burton diff --git a/docs/index.rst b/docs/index.rst index b22a68e..5bc2d09 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -45,6 +45,18 @@ Learn more about YAML support in the :ref:`tap-version-13` section. $ pip install tap.py[yaml] +Quickstart +---------- + +tappy can run like the built-in ``unittest`` discovery runner. + +.. code-block:: console + + $ python -m tap + +This should be enough to run a unittest-based test suite +and output TAP to the console. + Documentation ------------- diff --git a/docs/producers.rst b/docs/producers.rst index e44f225..ad5434f 100644 --- a/docs/producers.rst +++ b/docs/producers.rst @@ -13,6 +13,8 @@ and support for `pytest `_. for the **nose** testing tool. * tappy for **pytest** - tappy provides a plugin called ``tap`` for the **pytest** testing tool. +* tappy as the test runner - tappy can run like ``python -m unittest``. + Run your test suite with ``python -m tap``. By default, the producers will create one TAP file for each ``TestCase`` executed by the test suite. diff --git a/docs/releases.rst b/docs/releases.rst index 01be5be..fe92db5 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -6,6 +6,7 @@ Version 3.0, To Be Released * Drop support for Python 2 (it is end-of-life). * Add support for subtests. +* Run a test suite with ``python -m tap``. * Discontinue use of Pipenv for managing development. Version 2.6.2, Released October 20, 2019 diff --git a/tap/__main__.py b/tap/__main__.py new file mode 100644 index 0000000..5fa9af8 --- /dev/null +++ b/tap/__main__.py @@ -0,0 +1,3 @@ +from tap.main import main_module + +main_module() diff --git a/tap/main.py b/tap/main.py index 9da91a9..af7650b 100644 --- a/tap/main.py +++ b/tap/main.py @@ -6,6 +6,7 @@ from tap.i18n import _ from tap.loader import Loader +from tap.runner import TAPTestRunner def main(argv=sys.argv, stream=sys.stderr): @@ -71,3 +72,10 @@ def get_status(result): return 0 else: return 1 + + +def main_module(): + """Entry point for running as ``python -m tap``.""" + runner = TAPTestRunner() + runner.set_stream(True) + unittest.main(module=None, testRunner=runner) diff --git a/tap/tests/test_main.py b/tap/tests/test_main.py index 01e72fe..be85200 100644 --- a/tap/tests/test_main.py +++ b/tap/tests/test_main.py @@ -5,7 +5,7 @@ from unittest import mock from tap.loader import Loader -from tap.main import build_suite, get_status, main, parse_args +from tap.main import build_suite, get_status, main, main_module, parse_args from tap.tests import TestCase @@ -53,3 +53,11 @@ def test_when_no_pipe_to_stdin(self, print_help, sys_exit, mock_stdin): parse_args(argv) self.assertTrue(print_help.called) self.assertTrue(sys_exit.called) + + +class TestMainModule(TestCase): + @mock.patch("tap.main.unittest") + def test_main_set_to_stream(self, mock_unittest): + main_module() + + mock_unittest.main.called diff --git a/tox.ini b/tox.ini index 2884586..72d5060 100644 --- a/tox.ini +++ b/tox.ini @@ -4,6 +4,7 @@ envlist = py36 pypy3 runner + module lint integration coverage @@ -28,6 +29,9 @@ commands = python tap/tests/run.py [testenv:runner] commands = python tap/tests/run.py +[testenv:module] +commands = python -m tap + [testenv:lint] deps = black From 116aa55f5d1ae1f82604ae377e07a751f61bb651 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Sat, 14 Dec 2019 21:20:22 -0500 Subject: [PATCH 35/43] Use PyYAML safe loader and remove deprecation warning. (#103) Fixes #102 --- setup.py | 2 +- tap/line.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 2d001da..4c59879 100644 --- a/setup.py +++ b/setup.py @@ -63,7 +63,7 @@ def run(self): zip_safe=False, platforms="any", install_requires=[], - extras_require={"yaml": ["more-itertools", "PyYAML"]}, + extras_require={"yaml": ["more-itertools", "PyYAML>=5.1"]}, classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Console", diff --git a/tap/line.py b/tap/line.py index 0d44ff8..fc8f8e2 100644 --- a/tap/line.py +++ b/tap/line.py @@ -96,7 +96,7 @@ def yaml_block(self): """ if LOAD_YAML and self._yaml_block is not None: try: - yaml_dict = yaml.load(self._yaml_block) + yaml_dict = yaml.load(self._yaml_block, Loader=yaml.SafeLoader) return yaml_dict except yaml.error.YAMLError: print("Error parsing yaml block. Check formatting.") From 154443d9a3387b07563c7557497b9d717f466605 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Fri, 10 Jan 2020 14:53:53 +0000 Subject: [PATCH 36/43] Improve phrasing of man page (#106) Co-authored-by: Nicolas Caniart --- docs/tappy.1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tappy.1.rst b/docs/tappy.1.rst index 0ee254d..6d59377 100644 --- a/docs/tappy.1.rst +++ b/docs/tappy.1.rst @@ -17,7 +17,7 @@ The :program:`tappy` command consumes the list of tap files given as *pathname* s and produces an output similar to what the regular text test-runner from python's :py:mod:`unittest` module would. If *pathname* points to a directory, -:program:`tappy` will look in that directory of ``*.tap`` +:program:`tappy` will look in that directory for ``*.tap`` files to consume. If you have a tool that consumes the `unittest` regular output, From 006a3a2431ccc96772dacca73ab74b848a9ace6d Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Fri, 10 Jan 2020 14:58:09 +0000 Subject: [PATCH 37/43] test_tracker.py: Fix test failure in non-English locales (#105) The header is only "# TAP results for FakeTestCase" if we are running in an English locale, or in a locale not otherwise supported by tappy. For example, test failure can be reproduced with: $ LANGUAGE=fr_FR.utf8 pytest-3 build/lib Signed-off-by: Simon McVittie --- AUTHORS | 1 + tap/tests/test_tracker.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index c472381..e87e3b8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -14,3 +14,4 @@ Contributors * Nicolas Caniart * Richard Bosworth * Ross Burton +* Simon McVittie diff --git a/tap/tests/test_tracker.py b/tap/tests/test_tracker.py index 447fe1d..684568b 100644 --- a/tap/tests/test_tracker.py +++ b/tap/tests/test_tracker.py @@ -234,7 +234,10 @@ def test_write_plan_first_streaming(self): tracker.generate_tap_reports() self.assertEqual( - stream.getvalue(), "1..123\n# TAP results for FakeTestCase\nok 1 YESSS!\n" + stream.getvalue(), + "1..123\n{header}\nok 1 YESSS!\n".format( + header=self._make_header("FakeTestCase") + ), ) self.assertFalse(os.path.exists(os.path.join(outdir, "FakeTestCase.tap"))) From 7fd2fa62504e6a0df4a907d4359acb04566e56f8 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Fri, 10 Jan 2020 10:10:20 -0500 Subject: [PATCH 38/43] Set release date for 3.0. --- docs/releases.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releases.rst b/docs/releases.rst index fe92db5..7669b08 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -1,8 +1,8 @@ Releases ======== -Version 3.0, To Be Released ---------------------------- +Version 3.0, Released January 10, 2020 +-------------------------------------- * Drop support for Python 2 (it is end-of-life). * Add support for subtests. From 7ab5bd73bb1ecdecb70ad80d74c2cd91d4a3a3f3 Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Fri, 10 Jan 2020 10:13:19 -0500 Subject: [PATCH 39/43] Add wheel as package to `bdist_wheel` is an available setup command. --- requirements-dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-dev.txt b/requirements-dev.txt index aebdd34..7d020b6 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,6 +7,7 @@ requests Sphinx tox twine +wheel # These are the optional dependencies to enable TAP version 13 support. more-itertools From f55883200a8e9a01b783aefad74a15a4940808a0 Mon Sep 17 00:00:00 2001 From: Erik Cederstrand Date: Thu, 27 Feb 2020 22:52:32 +0100 Subject: [PATCH 40/43] Add latest Python versions (#109) * Add latest Python versions * Add 3.8 and move custom includes to latest version * Announce Python 3.8 support in metadata * Document Python 3.8 support --- .travis.yml | 15 +++++++++------ README.md | 1 + docs/index.rst | 1 + docs/releases.rst | 5 +++++ setup.py | 1 + tox.ini | 2 ++ 6 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2d6972d..45292de 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,6 +12,9 @@ matrix: - os: linux python: 3.7 env: TOX_ENV=py37 + - os: linux + python: 3.8 + env: TOX_ENV=py38 - os: linux python: pypy3.5 env: TOX_ENV=pypy3 @@ -20,22 +23,22 @@ matrix: language: generic env: TOX_ENV=py37 - os: linux - python: 3.6 + python: 3.8 env: TOX_ENV=with_optional - os: linux - python: 3.7 + python: 3.8 env: TOX_ENV=runner - os: linux - python: 3.7 + python: 3.8 env: TOX_ENV=module - os: linux - python: 3.7 + python: 3.8 env: TOX_ENV=lint - os: linux - python: 3.7 + python: 3.8 env: TOX_ENV=integration - os: linux - python: 3.7 + python: 3.8 env: TOX_ENV=coverage install: - pip3 install tox diff --git a/README.md b/README.md index 365f5a4..daaea28 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ on Python 3.5, 3.6, 3.7, +3.8, and PyPy. It is continuously tested on Linux, OS X, and Windows. diff --git a/docs/index.rst b/docs/index.rst index 5bc2d09..79da86a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -28,6 +28,7 @@ Python 3.5, 3.6, 3.7, +3.8, and PyPy. It is continuously tested on Linux, OS X, and Windows. diff --git a/docs/releases.rst b/docs/releases.rst index 7669b08..dd80f97 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -1,6 +1,11 @@ Releases ======== +Version 3.1, To Be Released +--------------------------- + +* Add support for Python 3.8. + Version 3.0, Released January 10, 2020 -------------------------------------- diff --git a/setup.py b/setup.py index 4c59879..172e31e 100644 --- a/setup.py +++ b/setup.py @@ -73,6 +73,7 @@ def run(self): "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Testing", ], diff --git a/tox.ini b/tox.ini index 72d5060..b3e4037 100644 --- a/tox.ini +++ b/tox.ini @@ -2,6 +2,8 @@ envlist = py35 py36 + py37 + py38 pypy3 runner module From 015c21c1a98b88f23844a44e346604f2398b11d5 Mon Sep 17 00:00:00 2001 From: Erik Cederstrand Date: Fri, 28 Feb 2020 03:18:35 +0100 Subject: [PATCH 41/43] Replace global is_peekable with a check on every file (#108) * Replace global is_peekable with a check on every file Fixes #107 * peekable is only available in version 13 mode * Add test for parsing mixed-version TAP files * Fix linter errors by de-duplicating common idiom * blacken --- tap/parser.py | 6 +-- tap/tests/test_parser.py | 90 +++++++++++++++++++++++++++++++--------- 2 files changed, 71 insertions(+), 25 deletions(-) diff --git a/tap/parser.py b/tap/parser.py index 9ad7bee..637436b 100644 --- a/tap/parser.py +++ b/tap/parser.py @@ -60,9 +60,6 @@ class Parser(object): TAP_MINIMUM_DECLARED_VERSION = 13 - def __init__(self): - self._try_peeking = False - def parse_file(self, filename): """Parse a TAP file to an iterable of tap.line.Line objects. @@ -103,7 +100,6 @@ def parse(self, fh): if first_parsed.category == "version" and first_parsed.version >= 13: if ENABLE_VERSION_13: fh_new = peekable(itertools.chain([first_line], fh)) - self._try_peeking = True else: # pragma no cover print( """ @@ -157,7 +153,7 @@ def _parse_result(self, ok, match, fh=None): """Parse a matching result line into a result instance.""" peek_match = None try: - if fh is not None and self._try_peeking: + if fh is not None and ENABLE_VERSION_13 and isinstance(fh, peekable): peek_match = self.yaml_block_start.match(fh.peek()) except StopIteration: pass diff --git a/tap/tests/test_parser.py b/tap/tests/test_parser.py index 8e89ca0..431297a 100644 --- a/tap/tests/test_parser.py +++ b/tap/tests/test_parser.py @@ -10,6 +10,14 @@ from tap.parser import Parser +try: + import yaml + from more_itertools import peekable # noqa + + have_yaml = True +except ImportError: + have_yaml = False + @contextmanager def captured_output(): @@ -231,23 +239,74 @@ def test_parses_yaml(self): for line in parser.parse_text(sample): lines.append(line) - try: - import yaml - from more_itertools import peekable # noqa - + if have_yaml: converted_yaml = yaml.safe_load(u"""test: sample yaml""") self.assertEqual(4, len(lines)) self.assertEqual(13, lines[0].version) self.assertEqual(converted_yaml, lines[2].yaml_block) self.assertEqual("test", lines[3].category) self.assertIsNone(lines[3].yaml_block) - except ImportError: + else: self.assertEqual(7, len(lines)) self.assertEqual(13, lines[0].version) for l in list(range(3, 6)): self.assertEqual("unknown", lines[l].category) self.assertEqual("test", lines[6].category) + def test_parses_mixed(self): + # Test that we can parse both a version 13 and earlier version files + # using the same parser. Make sure that parsing works regardless of + # the order of the incoming documents. + sample_version_13 = inspect.cleandoc( + u"""TAP version 13 + 1..2 + ok 1 A passing version 13 test + --- + test: sample yaml + ... + not ok 2 A failing version 13 test""" + ) + sample_pre_13 = inspect.cleandoc( + """1..2 + ok 1 A passing pre-13 test + not ok 2 A failing pre-13 test""" + ) + + parser = Parser() + lines = [] + lines.extend(parser.parse_text(sample_version_13)) + lines.extend(parser.parse_text(sample_pre_13)) + if have_yaml: + self.assertEqual(13, lines[0].version) + self.assertEqual("A passing version 13 test", lines[2].description) + self.assertEqual("A failing version 13 test", lines[3].description) + self.assertEqual("A passing pre-13 test", lines[5].description) + self.assertEqual("A failing pre-13 test", lines[6].description) + else: + self.assertEqual(13, lines[0].version) + self.assertEqual("A passing version 13 test", lines[2].description) + self.assertEqual("A failing version 13 test", lines[6].description) + self.assertEqual("A passing pre-13 test", lines[8].description) + self.assertEqual("A failing pre-13 test", lines[9].description) + + # Test parsing documents in reverse order + parser = Parser() + lines = [] + lines.extend(parser.parse_text(sample_pre_13)) + lines.extend(parser.parse_text(sample_version_13)) + if have_yaml: + self.assertEqual("A passing pre-13 test", lines[1].description) + self.assertEqual("A failing pre-13 test", lines[2].description) + self.assertEqual(13, lines[3].version) + self.assertEqual("A passing version 13 test", lines[5].description) + self.assertEqual("A failing version 13 test", lines[6].description) + else: + self.assertEqual("A passing pre-13 test", lines[1].description) + self.assertEqual("A failing pre-13 test", lines[2].description) + self.assertEqual(13, lines[3].version) + self.assertEqual("A passing version 13 test", lines[5].description) + self.assertEqual("A failing version 13 test", lines[9].description) + def test_parses_yaml_no_end(self): sample = inspect.cleandoc( u"""TAP version 13 @@ -263,17 +322,14 @@ def test_parses_yaml_no_end(self): for line in parser.parse_text(sample): lines.append(line) - try: - import yaml - from more_itertools import peekable # noqa - + if have_yaml: converted_yaml = yaml.safe_load(u"""test: sample yaml""") self.assertEqual(4, len(lines)) self.assertEqual(13, lines[0].version) self.assertEqual(converted_yaml, lines[2].yaml_block) self.assertEqual("test", lines[3].category) self.assertIsNone(lines[3].yaml_block) - except ImportError: + else: self.assertEqual(6, len(lines)) self.assertEqual(13, lines[0].version) for l in list(range(3, 5)): @@ -300,10 +356,7 @@ def test_parses_yaml_more_complex(self): for line in parser.parse_text(sample): lines.append(line) - try: - import yaml - from more_itertools import peekable # noqa - + if have_yaml: converted_yaml = yaml.safe_load( u""" message: test @@ -317,7 +370,7 @@ def test_parses_yaml_more_complex(self): self.assertEqual(3, len(lines)) self.assertEqual(13, lines[0].version) self.assertEqual(converted_yaml, lines[2].yaml_block) - except ImportError: + else: self.assertEqual(11, len(lines)) self.assertEqual(13, lines[0].version) for l in list(range(3, 11)): @@ -395,10 +448,7 @@ def test_malformed_yaml(self): for line in parser.parse_text(sample): lines.append(line) - try: - import yaml # noqa - from more_itertools import peekable # noqa - + if have_yaml: self.assertEqual(4, len(lines)) self.assertEqual(13, lines[0].version) with captured_output() as (out, _): @@ -408,7 +458,7 @@ def test_malformed_yaml(self): ) self.assertEqual("test", lines[3].category) self.assertIsNone(lines[3].yaml_block) - except ImportError: + else: self.assertEqual(8, len(lines)) self.assertEqual(13, lines[0].version) for l in list(range(3, 7)): From 0aedb990c2f4e0bdfcfdd1a9a22ae79fc06b59f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dato=20Sim=C3=B3?= Date: Sat, 9 May 2020 22:50:40 -0300 Subject: [PATCH 42/43] Fix parsing of multi-line strings in YAML blocks (#112) Closes #111. --- AUTHORS | 1 + docs/releases.rst | 1 + tap/parser.py | 3 ++- tap/tests/test_parser.py | 14 ++++++++++---- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index e87e3b8..3735496 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3,6 +3,7 @@ tappy was originally created by Matt Layman. Contributors ------------ +* Adeodato Simó * Andrew McNamara * Chris Clarke * Erik Cederstrand diff --git a/docs/releases.rst b/docs/releases.rst index dd80f97..7f708c7 100644 --- a/docs/releases.rst +++ b/docs/releases.rst @@ -5,6 +5,7 @@ Version 3.1, To Be Released --------------------------- * Add support for Python 3.8. +* Fix parsing of multi-line strings in YAML blocks (#111) Version 3.0, Released January 10, 2020 -------------------------------------- diff --git a/tap/parser.py b/tap/parser.py index 637436b..2f28bd8 100644 --- a/tap/parser.py +++ b/tap/parser.py @@ -181,7 +181,8 @@ def _extract_yaml_block(self, indent, fh): try: next(fh) while indent_match.match(fh.peek()): - raw_yaml.append(next(fh).replace(indent, "", 1)) + yaml_line = next(fh).replace(indent, "", 1) + raw_yaml.append(yaml_line.rstrip("\n")) # check for the end and stop adding yaml if encountered if self.yaml_block_end.match(fh.peek()): next(fh) diff --git a/tap/tests/test_parser.py b/tap/tests/test_parser.py index 431297a..ac8f3d3 100644 --- a/tap/tests/test_parser.py +++ b/tap/tests/test_parser.py @@ -348,7 +348,12 @@ def test_parses_yaml_more_complex(self): got: - foo expect: - - bar""" + - bar + output: |- + a multiline string + must be handled properly + even with | pipes + | here > and: there""" ) parser = Parser() lines = [] @@ -358,20 +363,21 @@ def test_parses_yaml_more_complex(self): if have_yaml: converted_yaml = yaml.safe_load( - u""" + u''' message: test severity: fail data: got: - foo expect: - - bar""" + - bar + output: "a multiline string\\nmust be handled properly\\neven with | pipes\\n| here > and: there"''' ) self.assertEqual(3, len(lines)) self.assertEqual(13, lines[0].version) self.assertEqual(converted_yaml, lines[2].yaml_block) else: - self.assertEqual(11, len(lines)) + self.assertEqual(16, len(lines)) self.assertEqual(13, lines[0].version) for l in list(range(3, 11)): self.assertEqual("unknown", lines[l].category) From 0c38a487d6e0113412902ab7a521120cf9da332f Mon Sep 17 00:00:00 2001 From: Matt Layman Date: Thu, 4 Feb 2021 10:49:44 -0500 Subject: [PATCH 43/43] Fix lint issues. --- tap/tests/test_parser.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tap/tests/test_parser.py b/tap/tests/test_parser.py index ac8f3d3..94850bc 100644 --- a/tap/tests/test_parser.py +++ b/tap/tests/test_parser.py @@ -249,8 +249,8 @@ def test_parses_yaml(self): else: self.assertEqual(7, len(lines)) self.assertEqual(13, lines[0].version) - for l in list(range(3, 6)): - self.assertEqual("unknown", lines[l].category) + for line_index in list(range(3, 6)): + self.assertEqual("unknown", lines[line_index].category) self.assertEqual("test", lines[6].category) def test_parses_mixed(self): @@ -332,8 +332,8 @@ def test_parses_yaml_no_end(self): else: self.assertEqual(6, len(lines)) self.assertEqual(13, lines[0].version) - for l in list(range(3, 5)): - self.assertEqual("unknown", lines[l].category) + for line_index in list(range(3, 5)): + self.assertEqual("unknown", lines[line_index].category) self.assertEqual("test", lines[5].category) def test_parses_yaml_more_complex(self): @@ -371,7 +371,7 @@ def test_parses_yaml_more_complex(self): - foo expect: - bar - output: "a multiline string\\nmust be handled properly\\neven with | pipes\\n| here > and: there"''' + output: "a multiline string\\nmust be handled properly\\neven with | pipes\\n| here > and: there"''' # noqa ) self.assertEqual(3, len(lines)) self.assertEqual(13, lines[0].version) @@ -379,8 +379,8 @@ def test_parses_yaml_more_complex(self): else: self.assertEqual(16, len(lines)) self.assertEqual(13, lines[0].version) - for l in list(range(3, 11)): - self.assertEqual("unknown", lines[l].category) + for line_index in list(range(3, 11)): + self.assertEqual("unknown", lines[line_index].category) def test_parses_yaml_no_association(self): sample = inspect.cleandoc( @@ -403,8 +403,8 @@ def test_parses_yaml_no_association(self): self.assertEqual(13, lines[0].version) self.assertIsNone(lines[2].yaml_block) self.assertEqual("diagnostic", lines[3].category) - for l in list(range(4, 7)): - self.assertEqual("unknown", lines[l].category) + for line_index in list(range(4, 7)): + self.assertEqual("unknown", lines[line_index].category) self.assertEqual("test", lines[7].category) def test_parses_yaml_no_start(self): @@ -425,8 +425,8 @@ def test_parses_yaml_no_start(self): self.assertEqual(6, len(lines)) self.assertEqual(13, lines[0].version) self.assertIsNone(lines[2].yaml_block) - for l in list(range(3, 5)): - self.assertEqual("unknown", lines[l].category) + for line_index in list(range(3, 5)): + self.assertEqual("unknown", lines[line_index].category) self.assertEqual("test", lines[5].category) def test_malformed_yaml(self): @@ -467,8 +467,8 @@ def test_malformed_yaml(self): else: self.assertEqual(8, len(lines)) self.assertEqual(13, lines[0].version) - for l in list(range(3, 7)): - self.assertEqual("unknown", lines[l].category) + for line_index in list(range(3, 7)): + self.assertEqual("unknown", lines[line_index].category) self.assertEqual("test", lines[7].category) self.assertEqual(yaml_err, parse_out.getvalue().strip())