From 5acbd7bc86e2f71bae31ad6f0fb1ed6d0e76e562 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 22 Sep 2015 10:54:37 -0300 Subject: [PATCH 01/13] Fix git checkout command in CONTRIBUTING.rst --- CONTRIBUTING.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 7cb0d64dd..8e7055699 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -153,11 +153,11 @@ but here is a simple overview: $ cd pytest # now, to fix a bug create your own branch off "master": - $ git checkout master -b your-bugfix-branch-name + $ git checkout -b your-bugfix-branch-name master # or to instead add a feature create your own branch off "features": - $ git checkout features -b your-feature-branch-name + $ git checkout -b your-feature-branch-name features Given we have "major.minor.micro" version numbers, bugfixes will usually be released in micro releases whereas features will be released in From 29f4da93d4137862129101e8bd1f649a33b8d290 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 22 Sep 2015 16:28:19 +0200 Subject: [PATCH 02/13] handle access errors when writing cache files silently as pytest warning, fixes #1039 --- _pytest/cacheprovider.py | 20 ++++++++++++++++---- testing/test_cache.py | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/_pytest/cacheprovider.py b/_pytest/cacheprovider.py index 977647fee..6fa92e59d 100755 --- a/_pytest/cacheprovider.py +++ b/_pytest/cacheprovider.py @@ -69,10 +69,22 @@ class Cache(object): like e. g. lists of dictionaries. """ path = self._getvaluepath(key) - path.dirpath().ensure_dir() - with path.open("w") as f: - self.trace("cache-write %s: %r" % (key, value,)) - json.dump(value, f, indent=2, sort_keys=True) + try: + path.dirpath().ensure_dir() + except (py.error.EEXIST, py.error.EACCES): + self.config.warn( + code='I9', message='cache could not create cache path %s' % (path,) + ) + return + try: + f = path.open('w') + except py.error.ENOTDIR: + self.config.warn( + code='I9', message='cache could not write path %s' % (path,)) + else: + with f: + self.trace("cache-write %s: %r" % (key, value,)) + json.dump(value, f, indent=2, sort_keys=True) class LFPlugin: diff --git a/testing/test_cache.py b/testing/test_cache.py index 8eac4e8e0..5205014e3 100755 --- a/testing/test_cache.py +++ b/testing/test_cache.py @@ -25,6 +25,21 @@ class TestNewAPI: val = config.cache.get("key/name", -2) assert val == -2 + def test_cache_writefail_cachfile_silent(self, testdir): + testdir.makeini("[pytest]") + testdir.tmpdir.join('.cache').write('gone wrong') + config = testdir.parseconfigure() + cache = config.cache + cache.set('test/broken', []) + + def test_cache_writefail_permissions(selfself, testdir): + testdir.makeini("[pytest]") + testdir.tmpdir.ensure_dir('.cache').chmod(0) + config = testdir.parseconfigure() + cache = config.cache + cache.set('test/broken', []) + + def test_config_cache(self, testdir): testdir.makeconftest(""" def pytest_configure(config): From 1d2afada83aa531db959ca455f4d9d564d259023 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 22 Sep 2015 11:30:00 -0300 Subject: [PATCH 03/13] Minor typo in CHANGELOG --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index e2ecbef59..daabcb5da 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,7 +12,7 @@ - Fix issue #411: Add __eq__ method to assertion comparison example. Thanks Ben Webb. -- fix issue 877: propperly handle assertion explanations with non-ascii repr +- fix issue 877: properly handle assertion explanations with non-ascii repr Thanks Mathieu Agopian for the report and Ronny Pfannschmidt for the PR. From 6e519183532a1f857fdd3c434e9595a46b13503d Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 22 Sep 2015 16:33:27 +0200 Subject: [PATCH 04/13] update changelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index e2ecbef59..820c61c42 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ - fix issue 877: propperly handle assertion explanations with non-ascii repr Thanks Mathieu Agopian for the report and Ronny Pfannschmidt for the PR. +- fix issue 1029: transform errors when writing cache values into pytest-warnings 2.8.0 ----------------------------- From dc1ce51ac2db4f7765e45862b4cc4f12d4561877 Mon Sep 17 00:00:00 2001 From: Abhijeet Kasurde Date: Tue, 22 Sep 2015 20:15:49 +0530 Subject: [PATCH 05/13] Added minor documentation change Signed-off-by: Abhijeet Kasurde --- _pytest/python.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/python.py b/_pytest/python.py index fe93c938e..5d89a4be5 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -854,7 +854,7 @@ class Metafunc(FuncargnamesCompatAttr): :arg argvalues: The list of argvalues determines how often a test is invoked with different argument values. If only one - argname was specified argvalues is a list of simple values. If N + argname was specified argvalues is a list of values. If N argnames were specified, argvalues must be a list of N-tuples, where each tuple-element specifies a value for its respective argname. From ea9a491fb3aae3580e17a1c1ef073afe7860df0d Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 22 Sep 2015 20:24:37 +0200 Subject: [PATCH 06/13] add an acceptance test for cache write errors --- _pytest/cacheprovider.py | 2 +- testing/test_cache.py | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/_pytest/cacheprovider.py b/_pytest/cacheprovider.py index 6fa92e59d..e4ae64ab3 100755 --- a/_pytest/cacheprovider.py +++ b/_pytest/cacheprovider.py @@ -73,7 +73,7 @@ class Cache(object): path.dirpath().ensure_dir() except (py.error.EEXIST, py.error.EACCES): self.config.warn( - code='I9', message='cache could not create cache path %s' % (path,) + code='I9', message='could not create cache path %s' % (path,) ) return try: diff --git a/testing/test_cache.py b/testing/test_cache.py index 5205014e3..bd33c1c7d 100755 --- a/testing/test_cache.py +++ b/testing/test_cache.py @@ -32,13 +32,26 @@ class TestNewAPI: cache = config.cache cache.set('test/broken', []) - def test_cache_writefail_permissions(selfself, testdir): + def test_cache_writefail_permissions(self, testdir): testdir.makeini("[pytest]") testdir.tmpdir.ensure_dir('.cache').chmod(0) config = testdir.parseconfigure() cache = config.cache cache.set('test/broken', []) + def test_cache_failure_warns(self, testdir): + testdir.tmpdir.ensure_dir('.cache').chmod(0) + testdir.makepyfile(""" + def test_pass(): + pass + + """) + result = testdir.runpytest('-rw') + assert result.ret == 0 + result.stdout.fnmatch_lines([ + "*could not create cache path*", + "*1 pytest-warnings*", + ]) def test_config_cache(self, testdir): testdir.makeconftest(""" From 7f776fe19ad05d5180d7ae3efc3674e94ccba365 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Tue, 22 Sep 2015 20:49:11 +0200 Subject: [PATCH 07/13] skip chmod using cache access warning tests on windows --- testing/test_cache.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/testing/test_cache.py b/testing/test_cache.py index bd33c1c7d..0538be9d7 100755 --- a/testing/test_cache.py +++ b/testing/test_cache.py @@ -1,3 +1,4 @@ +import sys import pytest import os import shutil @@ -32,6 +33,7 @@ class TestNewAPI: cache = config.cache cache.set('test/broken', []) + @pytest.mark.skipif(sys.platform.startswith('win'), reason='no chmod on windows') def test_cache_writefail_permissions(self, testdir): testdir.makeini("[pytest]") testdir.tmpdir.ensure_dir('.cache').chmod(0) @@ -39,6 +41,7 @@ class TestNewAPI: cache = config.cache cache.set('test/broken', []) + @pytest.mark.skipif(sys.platform.startswith('win'), reason='no chmod on windows') def test_cache_failure_warns(self, testdir): testdir.tmpdir.ensure_dir('.cache').chmod(0) testdir.makepyfile(""" From 661495e5c58996ab6ae3a5353cae3b95dbfa1965 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 21 Sep 2015 02:39:34 -0300 Subject: [PATCH 08/13] Write failing test for parametrized tests with unmarshable parameters Related to #1030; committing directly to pytest repository to get feedback from others on how to proceed. --- testing/test_cache.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/testing/test_cache.py b/testing/test_cache.py index 0538be9d7..20a6cf78a 100755 --- a/testing/test_cache.py +++ b/testing/test_cache.py @@ -269,6 +269,22 @@ class TestLastFailed: lastfailed = config.cache.get("cache/lastfailed", -1) assert not lastfailed + def test_non_serializable_parametrize(self, testdir): + """Test that failed parametrized tests with unmarshable parameters + don't break pytest-cache. + """ + testdir.makepyfile(r""" + import pytest + + @pytest.mark.parametrize('val', [ + b'\xac\x10\x02G', + ]) + def test_fail(val): + assert False + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines('*1 failed in*') + def test_lastfailed_collectfailure(self, testdir, monkeypatch): testdir.makepyfile(test_maybe=""" From e1063678f10b48c5872f6e9d829c21be9097748b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 22 Sep 2015 22:48:22 -0300 Subject: [PATCH 09/13] escape bytes when creating ids for parametrized values --- _pytest/python.py | 44 +++++++++++++++++++++++++++++++++++++- testing/python/metafunc.py | 16 ++++++++------ 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/_pytest/python.py b/_pytest/python.py index aa648408f..6548cdbf5 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -32,6 +32,9 @@ exc_clear = getattr(sys, 'exc_clear', lambda: None) # The type of re.compile objects is not exposed in Python. REGEX_TYPE = type(re.compile('')) +_PY3 = sys.version_info > (3, 0) +_PY2 = not _PY3 + if hasattr(inspect, 'signature'): def _format_args(func): @@ -1037,6 +1040,35 @@ class Metafunc(FuncargnamesCompatAttr): self._calls.append(cs) +if _PY3: + def _escape_bytes(val): + """ + If val is pure ascii, returns it as a str(), otherwise escapes + into a sequence of escaped bytes: + b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6' + + note: + the obvious "v.decode('unicode-escape')" will return + valid utf-8 unicode if it finds them in the string, but we + want to return escaped bytes for any byte, even if they match + a utf-8 string. + """ + # source: http://goo.gl/bGsnwC + import codecs + encoded_bytes, _ = codecs.escape_encode(val) + return encoded_bytes.decode('ascii') +else: + def _escape_bytes(val): + """ + In py2 bytes and str are the same, so return it unchanged if it + is a full ascii string, otherwise escape it into its binary form. + """ + try: + return val.encode('ascii') + except UnicodeDecodeError: + return val.encode('string-escape') + + def _idval(val, argname, idx, idfn): if idfn: try: @@ -1046,7 +1078,9 @@ def _idval(val, argname, idx, idfn): except Exception: pass - if isinstance(val, (float, int, str, bool, NoneType)): + if isinstance(val, bytes): + return _escape_bytes(val) + elif isinstance(val, (float, int, str, bool, NoneType)): return str(val) elif isinstance(val, REGEX_TYPE): return val.pattern @@ -1054,6 +1088,14 @@ def _idval(val, argname, idx, idfn): return str(val) elif isclass(val) and hasattr(val, '__name__'): return val.__name__ + elif _PY2 and isinstance(val, unicode): + # special case for python 2: if a unicode string is + # convertible to ascii, return it as an str() object instead + try: + return str(val) + except UnicodeDecodeError: + # fallthrough + pass return str(argname)+str(idx) def _idvalset(idx, valset, argnames, idfn): diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 04e4a4aeb..d0df62f81 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -129,11 +129,12 @@ class TestMetafunc: (object(), object())]) assert result == ["a0-1.0", "a1-b1"] # unicode mixing, issue250 - result = idmaker((py.builtin._totext("a"), "b"), [({}, '\xc3\xb4')]) - assert result == ['a0-\xc3\xb4'] + result = idmaker((py.builtin._totext("a"), "b"), [({}, b'\xc3\xb4')]) + assert result == ['a0-\\xc3\\xb4'] def test_idmaker_native_strings(self): from _pytest.python import idmaker + totext = py.builtin._totext result = idmaker(("a", "b"), [(1.0, -1.1), (2, -202), ("three", "three hundred"), @@ -143,7 +144,9 @@ class TestMetafunc: (str, int), (list("six"), [66, 66]), (set([7]), set("seven")), - (tuple("eight"), (8, -8, 8)) + (tuple("eight"), (8, -8, 8)), + (b'\xc3\xb4', b"name"), + (b'\xc3\xb4', totext("other")), ]) assert result == ["1.0--1.1", "2--202", @@ -154,7 +157,10 @@ class TestMetafunc: "str-int", "a7-b7", "a8-b8", - "a9-b9"] + "a9-b9", + "\\xc3\\xb4-name", + "\\xc3\\xb4-other", + ] def test_idmaker_enum(self): from _pytest.python import idmaker @@ -312,7 +318,6 @@ class TestMetafunc: "*uses no fixture 'y'*", ]) - @pytest.mark.xfail @pytest.mark.issue714 def test_parametrize_uses_no_fixture_error_indirect_true(self, testdir): testdir.makepyfile(""" @@ -333,7 +338,6 @@ class TestMetafunc: "*uses no fixture 'y'*", ]) - @pytest.mark.xfail @pytest.mark.issue714 def test_parametrize_indirect_uses_no_fixture_error_indirect_list(self, testdir): testdir.makepyfile(""" From 716fa97fa1edc72ce9011c85d41cb98460a1f93d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 22 Sep 2015 23:21:36 -0300 Subject: [PATCH 10/13] Update CHANGELOG --- CHANGELOG | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index f04362ade..8fb095796 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,13 @@ - Fix issue #766 by removing documentation references to distutils. Thanks Russel Winder. +- Fix issue #1030: now byte-strings are escaped to produce item node ids + to make them always serializable. + Thanks Andy Freeland for the report and Bruno Oliveira for the PR. + +- Python 2: if unicode parametrized values are convertible to ascii, their + ascii representation is used for the node id. + - Fix issue #411: Add __eq__ method to assertion comparison example. Thanks Ben Webb. From 25d2cc4604accdbaf7d69f1869474c2f2ea8cfba Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 22 Sep 2015 23:39:48 -0300 Subject: [PATCH 11/13] Add docstring to cache fixture Fixes #1049 --- _pytest/cacheprovider.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/_pytest/cacheprovider.py b/_pytest/cacheprovider.py index e4ae64ab3..a1f728d9c 100755 --- a/_pytest/cacheprovider.py +++ b/_pytest/cacheprovider.py @@ -186,8 +186,20 @@ def pytest_configure(config): @pytest.fixture def cache(request): + """ + Return a cache object that can persist state between testing sessions. + + cache.get(key, default) + cache.set(key, value) + + Keys must be strings not containing a "/" separator. Add a unique identifier + (such as plugin/app name) to avoid clashes with other cache users. + + Values can be any object handled by the json stdlib module. + """ return request.config.cache + def pytest_report_header(config): if config.option.verbose: relpath = py.path.local().bestrelpath(config.cache._cachedir) From 79ebca3f30e13b2e61fe476b10200b5923d54f6d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 23 Sep 2015 01:09:09 -0300 Subject: [PATCH 12/13] (w)warnings -> (w)pytest-warnings in "-r chars" help --- _pytest/terminal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/terminal.py b/_pytest/terminal.py index 21bdc9fc9..ce4a88bc0 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -22,7 +22,7 @@ def pytest_addoption(parser): group._addoption('-r', action="store", dest="reportchars", default=None, metavar="chars", help="show extra test summary info as specified by chars (f)ailed, " - "(E)error, (s)skipped, (x)failed, (X)passed (w)warnings (a)all.") + "(E)error, (s)skipped, (x)failed, (X)passed (w)pytest-warnings (a)all.") group._addoption('-l', '--showlocals', action="store_true", dest="showlocals", default=False, help="show locals in tracebacks (disabled by default).") From 730114d088ec95323654e39259ccc77f7d0852ec Mon Sep 17 00:00:00 2001 From: Michael Howitz Date: Wed, 23 Sep 2015 08:53:13 +0200 Subject: [PATCH 13/13] Remove duplicate line. --- CHANGELOG | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f04362ade..0ab31e994 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -193,8 +193,6 @@ - fix issue714: add ability to apply indirect=True parameter on particular argnames. Thanks Elizaveta239. -- fix issue714: add ability to apply indirect=True parameter on particular argnames. - - fix issue890: changed extension of all documentation files from ``txt`` to ``rst``. Thanks to Abhijeet for the PR.