Merge branch 'master' into features
This commit is contained in:
		
						commit
						4867554eec
					
				
							
								
								
									
										12
									
								
								CHANGELOG
								
								
								
								
							
							
						
						
									
										12
									
								
								CHANGELOG
								
								
								
								
							|  | @ -12,13 +12,21 @@ | ||||||
| - Fix issue #766 by removing documentation references to distutils. | - Fix issue #766 by removing documentation references to distutils. | ||||||
|   Thanks Russel Winder. |   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. | - Fix issue #411: Add __eq__ method to assertion comparison example. | ||||||
|   Thanks Ben Webb. |   Thanks Ben Webb. | ||||||
| - Fix issue #653: deprecated_call can be used as context manager. | - Fix issue #653: deprecated_call can be used as context manager. | ||||||
| 
 | 
 | ||||||
| - 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. |   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 | 2.8.0 | ||||||
| ----------------------------- | ----------------------------- | ||||||
|  | @ -196,8 +204,6 @@ | ||||||
| - fix issue714: add ability to apply indirect=True parameter on particular argnames. | - fix issue714: add ability to apply indirect=True parameter on particular argnames. | ||||||
|   Thanks Elizaveta239. |   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 | - fix issue890: changed extension of all documentation files from ``txt`` to | ||||||
|   ``rst``. Thanks to Abhijeet for the PR. |   ``rst``. Thanks to Abhijeet for the PR. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -153,11 +153,11 @@ but here is a simple overview: | ||||||
|     $ cd pytest |     $ cd pytest | ||||||
|     # now, to fix a bug create your own branch off "master": |     # 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": |     # 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  |    Given we have "major.minor.micro" version numbers, bugfixes will usually  | ||||||
|    be released in micro releases whereas features will be released in  |    be released in micro releases whereas features will be released in  | ||||||
|  |  | ||||||
|  | @ -69,10 +69,22 @@ class Cache(object): | ||||||
|                like e. g. lists of dictionaries. |                like e. g. lists of dictionaries. | ||||||
|         """ |         """ | ||||||
|         path = self._getvaluepath(key) |         path = self._getvaluepath(key) | ||||||
|         path.dirpath().ensure_dir() |         try: | ||||||
|         with path.open("w") as f: |             path.dirpath().ensure_dir() | ||||||
|             self.trace("cache-write %s: %r" % (key, value,)) |         except (py.error.EEXIST, py.error.EACCES): | ||||||
|             json.dump(value, f, indent=2, sort_keys=True) |             self.config.warn( | ||||||
|  |                 code='I9', message='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: | class LFPlugin: | ||||||
|  | @ -174,8 +186,20 @@ def pytest_configure(config): | ||||||
| 
 | 
 | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
| def cache(request): | 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 |     return request.config.cache | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| def pytest_report_header(config): | def pytest_report_header(config): | ||||||
|     if config.option.verbose: |     if config.option.verbose: | ||||||
|         relpath = py.path.local().bestrelpath(config.cache._cachedir) |         relpath = py.path.local().bestrelpath(config.cache._cachedir) | ||||||
|  |  | ||||||
|  | @ -32,6 +32,9 @@ exc_clear = getattr(sys, 'exc_clear', lambda: None) | ||||||
| # The type of re.compile objects is not exposed in Python. | # The type of re.compile objects is not exposed in Python. | ||||||
| REGEX_TYPE = type(re.compile('')) | REGEX_TYPE = type(re.compile('')) | ||||||
| 
 | 
 | ||||||
|  | _PY3 = sys.version_info > (3, 0) | ||||||
|  | _PY2 = not _PY3 | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| if hasattr(inspect, 'signature'): | if hasattr(inspect, 'signature'): | ||||||
|     def _format_args(func): |     def _format_args(func): | ||||||
|  | @ -912,7 +915,7 @@ class Metafunc(FuncargnamesCompatAttr): | ||||||
| 
 | 
 | ||||||
|         :arg argvalues: The list of argvalues determines how often a |         :arg argvalues: The list of argvalues determines how often a | ||||||
|             test is invoked with different argument values.  If only one |             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, |             argnames were specified, argvalues must be a list of N-tuples, | ||||||
|             where each tuple-element specifies a value for its respective |             where each tuple-element specifies a value for its respective | ||||||
|             argname. |             argname. | ||||||
|  | @ -1037,6 +1040,35 @@ class Metafunc(FuncargnamesCompatAttr): | ||||||
|         self._calls.append(cs) |         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): | def _idval(val, argname, idx, idfn): | ||||||
|     if idfn: |     if idfn: | ||||||
|         try: |         try: | ||||||
|  | @ -1046,7 +1078,9 @@ def _idval(val, argname, idx, idfn): | ||||||
|         except Exception: |         except Exception: | ||||||
|             pass |             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) |         return str(val) | ||||||
|     elif isinstance(val, REGEX_TYPE): |     elif isinstance(val, REGEX_TYPE): | ||||||
|         return val.pattern |         return val.pattern | ||||||
|  | @ -1054,6 +1088,14 @@ def _idval(val, argname, idx, idfn): | ||||||
|         return str(val) |         return str(val) | ||||||
|     elif isclass(val) and hasattr(val, '__name__'): |     elif isclass(val) and hasattr(val, '__name__'): | ||||||
|         return 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) |     return str(argname)+str(idx) | ||||||
| 
 | 
 | ||||||
| def _idvalset(idx, valset, argnames, idfn): | def _idvalset(idx, valset, argnames, idfn): | ||||||
|  |  | ||||||
|  | @ -22,7 +22,7 @@ def pytest_addoption(parser): | ||||||
|     group._addoption('-r', |     group._addoption('-r', | ||||||
|          action="store", dest="reportchars", default=None, metavar="chars", |          action="store", dest="reportchars", default=None, metavar="chars", | ||||||
|          help="show extra test summary info as specified by chars (f)ailed, " |          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', |     group._addoption('-l', '--showlocals', | ||||||
|          action="store_true", dest="showlocals", default=False, |          action="store_true", dest="showlocals", default=False, | ||||||
|          help="show locals in tracebacks (disabled by default).") |          help="show locals in tracebacks (disabled by default).") | ||||||
|  |  | ||||||
|  | @ -129,11 +129,12 @@ class TestMetafunc: | ||||||
|                                       (object(), object())]) |                                       (object(), object())]) | ||||||
|         assert result == ["a0-1.0", "a1-b1"] |         assert result == ["a0-1.0", "a1-b1"] | ||||||
|         # unicode mixing, issue250 |         # unicode mixing, issue250 | ||||||
|         result = idmaker((py.builtin._totext("a"), "b"), [({}, '\xc3\xb4')]) |         result = idmaker((py.builtin._totext("a"), "b"), [({}, b'\xc3\xb4')]) | ||||||
|         assert result == ['a0-\xc3\xb4'] |         assert result == ['a0-\\xc3\\xb4'] | ||||||
| 
 | 
 | ||||||
|     def test_idmaker_native_strings(self): |     def test_idmaker_native_strings(self): | ||||||
|         from _pytest.python import idmaker |         from _pytest.python import idmaker | ||||||
|  |         totext = py.builtin._totext | ||||||
|         result = idmaker(("a", "b"), [(1.0, -1.1), |         result = idmaker(("a", "b"), [(1.0, -1.1), | ||||||
|                                       (2, -202), |                                       (2, -202), | ||||||
|                                       ("three", "three hundred"), |                                       ("three", "three hundred"), | ||||||
|  | @ -143,7 +144,9 @@ class TestMetafunc: | ||||||
|                                       (str, int), |                                       (str, int), | ||||||
|                                       (list("six"), [66, 66]), |                                       (list("six"), [66, 66]), | ||||||
|                                       (set([7]), set("seven")), |                                       (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", |         assert result == ["1.0--1.1", | ||||||
|                           "2--202", |                           "2--202", | ||||||
|  | @ -154,7 +157,10 @@ class TestMetafunc: | ||||||
|                           "str-int", |                           "str-int", | ||||||
|                           "a7-b7", |                           "a7-b7", | ||||||
|                           "a8-b8", |                           "a8-b8", | ||||||
|                           "a9-b9"] |                           "a9-b9", | ||||||
|  |                           "\\xc3\\xb4-name", | ||||||
|  |                           "\\xc3\\xb4-other", | ||||||
|  |                           ] | ||||||
| 
 | 
 | ||||||
|     def test_idmaker_enum(self): |     def test_idmaker_enum(self): | ||||||
|         from _pytest.python import idmaker |         from _pytest.python import idmaker | ||||||
|  | @ -312,7 +318,6 @@ class TestMetafunc: | ||||||
|             "*uses no fixture 'y'*", |             "*uses no fixture 'y'*", | ||||||
|         ]) |         ]) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.xfail |  | ||||||
|     @pytest.mark.issue714 |     @pytest.mark.issue714 | ||||||
|     def test_parametrize_uses_no_fixture_error_indirect_true(self, testdir): |     def test_parametrize_uses_no_fixture_error_indirect_true(self, testdir): | ||||||
|         testdir.makepyfile(""" |         testdir.makepyfile(""" | ||||||
|  | @ -333,7 +338,6 @@ class TestMetafunc: | ||||||
|             "*uses no fixture 'y'*", |             "*uses no fixture 'y'*", | ||||||
|         ]) |         ]) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.xfail |  | ||||||
|     @pytest.mark.issue714 |     @pytest.mark.issue714 | ||||||
|     def test_parametrize_indirect_uses_no_fixture_error_indirect_list(self, testdir): |     def test_parametrize_indirect_uses_no_fixture_error_indirect_list(self, testdir): | ||||||
|         testdir.makepyfile(""" |         testdir.makepyfile(""" | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | import sys | ||||||
| import pytest | import pytest | ||||||
| import os | import os | ||||||
| import shutil | import shutil | ||||||
|  | @ -25,6 +26,36 @@ class TestNewAPI: | ||||||
|         val = config.cache.get("key/name", -2) |         val = config.cache.get("key/name", -2) | ||||||
|         assert val == -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', []) | ||||||
|  | 
 | ||||||
|  |     @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) | ||||||
|  |         config = testdir.parseconfigure() | ||||||
|  |         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(""" | ||||||
|  |             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): |     def test_config_cache(self, testdir): | ||||||
|         testdir.makeconftest(""" |         testdir.makeconftest(""" | ||||||
|             def pytest_configure(config): |             def pytest_configure(config): | ||||||
|  | @ -238,6 +269,22 @@ class TestLastFailed: | ||||||
|         lastfailed = config.cache.get("cache/lastfailed", -1) |         lastfailed = config.cache.get("cache/lastfailed", -1) | ||||||
|         assert not lastfailed |         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): |     def test_lastfailed_collectfailure(self, testdir, monkeypatch): | ||||||
| 
 | 
 | ||||||
|         testdir.makepyfile(test_maybe=""" |         testdir.makepyfile(test_maybe=""" | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue