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. | ||||
|   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. | ||||
| - 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. | ||||
| 
 | ||||
| - fix issue 1029: transform errors when writing cache values into pytest-warnings | ||||
| 
 | ||||
| 2.8.0 | ||||
| ----------------------------- | ||||
|  | @ -196,8 +204,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. | ||||
| 
 | ||||
|  |  | |||
|  | @ -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  | ||||
|  |  | |||
|  | @ -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='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: | ||||
|  | @ -174,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) | ||||
|  |  | |||
|  | @ -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): | ||||
|  | @ -912,7 +915,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. | ||||
|  | @ -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): | ||||
|  |  | |||
|  | @ -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).") | ||||
|  |  | |||
|  | @ -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(""" | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| import sys | ||||
| import pytest | ||||
| import os | ||||
| import shutil | ||||
|  | @ -25,6 +26,36 @@ 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', []) | ||||
| 
 | ||||
|     @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): | ||||
|         testdir.makeconftest(""" | ||||
|             def pytest_configure(config): | ||||
|  | @ -238,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=""" | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue