diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index ad3fea61e..bf9fc199f 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -12,4 +12,4 @@ Here's a quick checklist that should be present in PRs: Unless your change is a trivial or a documentation fix (e.g., a typo or reword of a small section) please: -- [ ] Add yourself to `AUTHORS`; +- [ ] Add yourself to `AUTHORS`, in alphabetical order; diff --git a/AUTHORS b/AUTHORS index 6e341d64e..cc789b3b6 100644 --- a/AUTHORS +++ b/AUTHORS @@ -46,6 +46,7 @@ Dave Hunt David Díaz-Barquero David Mohr David Vierra +Daw-Ran Liou Denis Kirisov Diego Russo Dmitry Dygalo @@ -164,6 +165,7 @@ Stephan Obermann Tareq Alayan Ted Xiao Thomas Grainger +Tom Dalton Tom Viner Trevor Bekolay Tyler Goodlet diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 829f691ad..28269a311 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,46 @@ .. towncrier release notes start +Pytest 3.2.3 (2017-10-03) +========================= + +Bug Fixes +--------- + +- Fix crash in tab completion when no prefix is given. (`#2748 + `_) + +- The equality checking function (``__eq__``) of ``MarkDecorator`` returns + ``False`` if one object is not an instance of ``MarkDecorator``. (`#2758 + `_) + +- When running ``pytest --fixtures-per-test``: don't crash if an item has no + _fixtureinfo attribute (e.g. doctests) (`#2788 + `_) + + +Improved Documentation +---------------------- + +- In help text of ``-k`` option, add example of using ``not`` to not select + certain tests whose names match the provided expression. (`#1442 + `_) + +- Add note in ``parametrize.rst`` about calling ``metafunc.parametrize`` + multiple times. (`#1548 `_) + + +Trivial/Internal Changes +------------------------ + +- Set ``xfail_strict=True`` in pytest's own test suite to catch expected + failures as soon as they start to pass. (`#2722 + `_) + +- Fix typo in example of passing a callable to markers (in example/markers.rst) + (`#2765 `_) + + Pytest 3.2.2 (2017-09-06) ========================= diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index fa317fd1b..68db81398 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -120,7 +120,7 @@ the following: - PyPI presence with a ``setup.py`` that contains a license, ``pytest-`` prefixed name, version number, authors, short and long description. -- a ``tox.ini`` for running tests using `tox `_. +- a ``tox.ini`` for running tests using `tox `_. - a ``README.txt`` describing how to use the plugin and on which platforms it runs. @@ -177,7 +177,8 @@ Short version #. Write a ``changelog`` entry: ``changelog/2574.bugfix``, use issue id number and one of ``bugfix``, ``removal``, ``feature``, ``vendor``, ``doc`` or ``trivial`` for the issue type. -#. Add yourself to ``AUTHORS`` file if not there yet, in alphabetical order. +#. Unless your change is a trivial or a documentation fix (e.g., a typo or reword of a small section) please + add yourself to the ``AUTHORS`` file, in alphabetical order; Long version diff --git a/HOWTORELEASE.rst b/HOWTORELEASE.rst index c85fd9b99..48a3461d4 100644 --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -1,5 +1,9 @@ -How to release pytest --------------------------------------------- +Release Procedure +----------------- + +Our current policy for releasing is to aim for a bugfix every few weeks and a minor release every 2-3 months. The idea +is to get fixes and new features out instead of trying to cram a ton of features into a release and by consequence +taking a lot of time to make a new one. .. important:: @@ -21,7 +25,7 @@ How to release pytest #. Generate docs, changelog, announcements and upload a package to your ``devpi`` staging server:: - invoke generate.pre_release --password + invoke generate.pre-release --password If ``--password`` is not given, it is assumed the user is already logged in ``devpi``. If you don't have an account, please ask for one. @@ -49,7 +53,7 @@ How to release pytest #. Publish to PyPI:: - invoke generate.publish_release + invoke generate.publish-release where PYPI_NAME is the name of pypi.python.org as configured in your ``~/.pypirc`` file `for devpi `_. diff --git a/_pytest/_argcomplete.py b/_pytest/_argcomplete.py index 1ba1ecc1e..965ec7951 100644 --- a/_pytest/_argcomplete.py +++ b/_pytest/_argcomplete.py @@ -78,7 +78,8 @@ class FastFilesCompleter: completion = [] globbed = [] if '*' not in prefix and '?' not in prefix: - if prefix[-1] == os.path.sep: # we are on unix, otherwise no bash + # we are on unix, otherwise no bash + if not prefix or prefix[-1] == os.path.sep: globbed.extend(glob(prefix + '.*')) prefix += '*' globbed.extend(glob(prefix)) @@ -98,7 +99,7 @@ if os.environ.get('_ARGCOMPLETE'): filescompleter = FastFilesCompleter() def try_argcomplete(parser): - argcomplete.autocomplete(parser) + argcomplete.autocomplete(parser, always_complete_options=False) else: def try_argcomplete(parser): pass diff --git a/_pytest/_code/code.py b/_pytest/_code/code.py index cf10fb6bc..f3b7eedfc 100644 --- a/_pytest/_code/code.py +++ b/_pytest/_code/code.py @@ -250,7 +250,7 @@ class TracebackEntry(object): line = str(self.statement).lstrip() except KeyboardInterrupt: raise - except: + except: # noqa line = "???" return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line) @@ -338,16 +338,16 @@ class Traceback(list): # XXX needs a test key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno # print "checking for recursion at", key - l = cache.setdefault(key, []) - if l: + values = cache.setdefault(key, []) + if values: f = entry.frame loc = f.f_locals - for otherloc in l: + for otherloc in values: if f.is_true(f.eval(co_equal, __recursioncache_locals_1=loc, __recursioncache_locals_2=otherloc)): return i - l.append(entry.frame.f_locals) + values.append(entry.frame.f_locals) return None @@ -478,12 +478,12 @@ class FormattedExcinfo(object): s = str(source.getstatement(len(source) - 1)) except KeyboardInterrupt: raise - except: + except: # noqa try: s = str(source[-1]) except KeyboardInterrupt: raise - except: + except: # noqa return 0 return 4 + (len(s) - len(s.lstrip())) diff --git a/_pytest/_code/source.py b/_pytest/_code/source.py index e21fecb1e..fc4171264 100644 --- a/_pytest/_code/source.py +++ b/_pytest/_code/source.py @@ -254,7 +254,7 @@ def findsource(obj): sourcelines, lineno = py.std.inspect.findsource(obj) except py.builtin._sysex: raise - except: + except: # noqa return None, -1 source = Source() source.lines = [line.rstrip() for line in sourcelines] @@ -319,22 +319,22 @@ def get_statement_startend2(lineno, node): import ast # flatten all statements and except handlers into one lineno-list # AST's line numbers start indexing at 1 - l = [] + values = [] for x in ast.walk(node): if isinstance(x, _ast.stmt) or isinstance(x, _ast.ExceptHandler): - l.append(x.lineno - 1) + values.append(x.lineno - 1) for name in "finalbody", "orelse": val = getattr(x, name, None) if val: # treat the finally/orelse part as its own statement - l.append(val[0].lineno - 1 - 1) - l.sort() - insert_index = bisect_right(l, lineno) - start = l[insert_index - 1] - if insert_index >= len(l): + values.append(val[0].lineno - 1 - 1) + values.sort() + insert_index = bisect_right(values, lineno) + start = values[insert_index - 1] + if insert_index >= len(values): end = None else: - end = l[insert_index] + end = values[insert_index] return start, end diff --git a/_pytest/assertion/rewrite.py b/_pytest/assertion/rewrite.py index 992002b81..d48b6648f 100644 --- a/_pytest/assertion/rewrite.py +++ b/_pytest/assertion/rewrite.py @@ -210,7 +210,7 @@ class AssertionRewritingHook(object): mod.__cached__ = pyc mod.__loader__ = self py.builtin.exec_(co, mod.__dict__) - except: + except: # noqa if name in sys.modules: del sys.modules[name] raise @@ -595,23 +595,26 @@ class AssertionRewriter(ast.NodeVisitor): # docstrings and __future__ imports. aliases = [ast.alias(py.builtin.builtins.__name__, "@py_builtins"), ast.alias("_pytest.assertion.rewrite", "@pytest_ar")] - expect_docstring = True + doc = getattr(mod, "docstring", None) + expect_docstring = doc is None + if doc is not None and self.is_rewrite_disabled(doc): + return pos = 0 - lineno = 0 + lineno = 1 for item in mod.body: if (expect_docstring and isinstance(item, ast.Expr) and isinstance(item.value, ast.Str)): doc = item.value.s - if "PYTEST_DONT_REWRITE" in doc: - # The module has disabled assertion rewriting. + if self.is_rewrite_disabled(doc): return - lineno += len(doc) - 1 expect_docstring = False elif (not isinstance(item, ast.ImportFrom) or item.level > 0 or item.module != "__future__"): lineno = item.lineno break pos += 1 + else: + lineno = item.lineno imports = [ast.Import([alias], lineno=lineno, col_offset=0) for alias in aliases] mod.body[pos:pos] = imports @@ -637,6 +640,9 @@ class AssertionRewriter(ast.NodeVisitor): not isinstance(field, ast.expr)): nodes.append(field) + def is_rewrite_disabled(self, docstring): + return "PYTEST_DONT_REWRITE" in docstring + def variable(self): """Get a new variable.""" # Use a character invalid in python identifiers to avoid clashing. diff --git a/_pytest/assertion/util.py b/_pytest/assertion/util.py index 41e66448d..9f0092907 100644 --- a/_pytest/assertion/util.py +++ b/_pytest/assertion/util.py @@ -53,11 +53,11 @@ def _split_explanation(explanation): """ raw_lines = (explanation or u('')).split('\n') lines = [raw_lines[0]] - for l in raw_lines[1:]: - if l and l[0] in ['{', '}', '~', '>']: - lines.append(l) + for values in raw_lines[1:]: + if values and values[0] in ['{', '}', '~', '>']: + lines.append(values) else: - lines[-1] += '\\n' + l + lines[-1] += '\\n' + values return lines diff --git a/_pytest/config.py b/_pytest/config.py index 364ac13c6..19835d2c3 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -1170,10 +1170,10 @@ class Config(object): return [] if type == "pathlist": dp = py.path.local(self.inicfg.config.path).dirpath() - l = [] + values = [] for relpath in shlex.split(value): - l.append(dp.join(relpath, abs=True)) - return l + values.append(dp.join(relpath, abs=True)) + return values elif type == "args": return shlex.split(value) elif type == "linelist": @@ -1190,13 +1190,13 @@ class Config(object): except KeyError: return None modpath = py.path.local(mod.__file__).dirpath() - l = [] + values = [] for relroot in relroots: if not isinstance(relroot, py.path.local): relroot = relroot.replace("/", py.path.local.sep) relroot = modpath.join(relroot, abs=True) - l.append(relroot) - return l + values.append(relroot) + return values def _get_override_ini_value(self, name): value = None diff --git a/_pytest/doctest.py b/_pytest/doctest.py index cc505c8d0..4c05acddf 100644 --- a/_pytest/doctest.py +++ b/_pytest/doctest.py @@ -120,7 +120,7 @@ class DoctestItem(pytest.Item): lines = ["%03d %s" % (i + test.lineno + 1, x) for (i, x) in enumerate(lines)] # trim docstring error lines to 10 - lines = lines[example.lineno - 9:example.lineno + 1] + lines = lines[max(example.lineno - 9, 0):example.lineno + 1] else: lines = ['EXAMPLE LOCATION UNKNOWN, not showing all tests of that example'] indent = '>>>' diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index f57031e1a..8858a40e9 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -1,13 +1,14 @@ from __future__ import absolute_import, division, print_function -import sys - -from py._code.code import FormattedExcinfo - -import py -import warnings import inspect +import sys +import warnings + +import py +from py._code.code import FormattedExcinfo + import _pytest +from _pytest import nodes from _pytest._code.code import TerminalRepr from _pytest.compat import ( NOTSET, exc_clear, _format_args, @@ -15,9 +16,10 @@ from _pytest.compat import ( is_generator, isclass, getimfunc, getlocation, getfuncargnames, safe_getattr, + FuncargnamesCompatAttr, ) from _pytest.outcomes import fail, TEST_OUTCOME -from _pytest.compat import FuncargnamesCompatAttr + if sys.version_info[:2] == (2, 6): from ordereddict import OrderedDict @@ -458,13 +460,13 @@ class FixtureRequest(FuncargnamesCompatAttr): def _get_fixturestack(self): current = self - l = [] + values = [] while 1: fixturedef = getattr(current, "_fixturedef", None) if fixturedef is None: - l.reverse() - return l - l.append(fixturedef) + values.reverse() + return values + values.append(fixturedef) current = current._parent_request def _getfixturevalue(self, fixturedef): @@ -573,7 +575,6 @@ class SubRequest(FixtureRequest): self.param_index = param_index self.scope = scope self._fixturedef = fixturedef - self.addfinalizer = fixturedef.addfinalizer self._pyfuncitem = request._pyfuncitem self._fixture_values = request._fixture_values self._fixture_defs = request._fixture_defs @@ -584,6 +585,9 @@ class SubRequest(FixtureRequest): def __repr__(self): return "" % (self.fixturename, self._pyfuncitem) + def addfinalizer(self, finalizer): + self._fixturedef.addfinalizer(finalizer) + class ScopeMismatchError(Exception): """ A fixture function tries to use a different fixture function which @@ -747,7 +751,7 @@ class FixtureDef: try: func = self._finalizer.pop() func() - except: + except: # noqa exceptions.append(sys.exc_info()) if exceptions: e = exceptions[0] @@ -981,8 +985,8 @@ class FixtureManager: # by their test id) if p.basename.startswith("conftest.py"): nodeid = p.dirpath().relto(self.config.rootdir) - if p.sep != "/": - nodeid = nodeid.replace(p.sep, "/") + if p.sep != nodes.SEP: + nodeid = nodeid.replace(p.sep, nodes.SEP) self.parsefactories(plugin, nodeid) def _getautousenames(self, nodeid): @@ -1037,9 +1041,14 @@ class FixtureManager: if faclist: fixturedef = faclist[-1] if fixturedef.params is not None: - func_params = getattr(getattr(metafunc.function, 'parametrize', None), 'args', [[None]]) + parametrize_func = getattr(metafunc.function, 'parametrize', None) + func_params = getattr(parametrize_func, 'args', [[None]]) + func_kwargs = getattr(parametrize_func, 'kwargs', {}) # skip directly parametrized arguments - argnames = func_params[0] + if "argnames" in func_kwargs: + argnames = parametrize_func.kwargs["argnames"] + else: + argnames = func_params[0] if not isinstance(argnames, (tuple, list)): argnames = [x.strip() for x in argnames.split(",") if x.strip()] if argname not in func_params and argname not in argnames: @@ -1127,5 +1136,5 @@ class FixtureManager: def _matchfactories(self, fixturedefs, nodeid): for fixturedef in fixturedefs: - if nodeid.startswith(fixturedef.baseid): + if nodes.ischildnode(fixturedef.baseid, nodeid): yield fixturedef diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index ed3ba2e9a..7fb40dc35 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -17,6 +17,7 @@ import re import sys import time import pytest +from _pytest import nodes from _pytest.config import filename_arg # Python 2.X and 3.X compatibility @@ -252,7 +253,7 @@ def mangle_test_address(address): except ValueError: pass # convert file path to dotted path - names[0] = names[0].replace("/", '.') + names[0] = names[0].replace(nodes.SEP, '.') names[0] = _py_ext_re.sub("", names[0]) # put any params back names[-1] += possible_open_bracket + params diff --git a/_pytest/main.py b/_pytest/main.py index 472cf77b1..eacae8dab 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -6,6 +6,7 @@ import os import sys import _pytest +from _pytest import nodes import _pytest._code import py try: @@ -14,8 +15,8 @@ except ImportError: from UserDict import DictMixin as MappingMixin from _pytest.config import directory_arg, UsageError, hookimpl -from _pytest.runner import collect_one_node from _pytest.outcomes import exit +from _pytest.runner import collect_one_node tracebackcutdir = py.path.local(_pytest.__file__).dirpath() @@ -117,7 +118,7 @@ def wrap_session(config, doit): excinfo.typename, excinfo.value.msg)) config.hook.pytest_keyboard_interrupt(excinfo=excinfo) session.exitstatus = EXIT_INTERRUPTED - except: + except: # noqa excinfo = _pytest._code.ExceptionInfo() config.notify_exception(excinfo, config.option) session.exitstatus = EXIT_INTERNALERROR @@ -374,7 +375,7 @@ class Node(object): res = function() except py.builtin._sysex: raise - except: + except: # noqa failure = sys.exc_info() setattr(self, exattrname, failure) raise @@ -516,7 +517,7 @@ class FSCollector(Collector): rel = fspath.relto(parent.fspath) if rel: name = rel - name = name.replace(os.sep, "/") + name = name.replace(os.sep, nodes.SEP) super(FSCollector, self).__init__(name, parent, config, session) self.fspath = fspath @@ -527,10 +528,11 @@ class FSCollector(Collector): def _makeid(self): relpath = self.fspath.relto(self.config.rootdir) + if not relpath: relpath = self._check_initialpaths_for_relpath() - if os.sep != "/": - relpath = relpath.replace(os.sep, "/") + if os.sep != nodes.SEP: + relpath = relpath.replace(os.sep, nodes.SEP) return relpath diff --git a/_pytest/mark.py b/_pytest/mark.py index d9af0ffaa..454722ca2 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -91,7 +91,8 @@ def pytest_addoption(parser): "where all names are substring-matched against test names " "and their parent classes. Example: -k 'test_method or test_" "other' matches all test functions and classes whose name " - "contains 'test_method' or 'test_other'. " + "contains 'test_method' or 'test_other', while -k 'not test_method' " + "matches those that don't contain 'test_method' in their names. " "Additionally keywords are matched to classes and functions " "containing extra names in their 'extra_keyword_matches' set, " "as well as functions which have names assigned directly to them." @@ -269,11 +270,12 @@ class MarkGenerator: return except AttributeError: pass - self._markers = l = set() + self._markers = values = set() for line in self._config.getini("markers"): - beginning = line.split(":", 1) - x = beginning[0].split("(", 1)[0] - l.add(x) + marker, _ = line.split(":", 1) + marker = marker.rstrip() + x = marker.split("(", 1)[0] + values.add(x) if name not in self._markers: raise AttributeError("%r not a registered marker" % (name,)) @@ -382,7 +384,7 @@ def store_mark(obj, mark): """ assert isinstance(mark, Mark), mark # always reassign name to avoid updating pytestmark - # in a referene that was only borrowed + # in a reference that was only borrowed obj.pytestmark = get_unpacked_marks(obj) + [mark] diff --git a/_pytest/nodes.py b/_pytest/nodes.py new file mode 100644 index 000000000..ad3af2ce6 --- /dev/null +++ b/_pytest/nodes.py @@ -0,0 +1,37 @@ +SEP = "/" + + +def _splitnode(nodeid): + """Split a nodeid into constituent 'parts'. + + Node IDs are strings, and can be things like: + '' + 'testing/code' + 'testing/code/test_excinfo.py' + 'testing/code/test_excinfo.py::TestFormattedExcinfo::()' + + Return values are lists e.g. + [] + ['testing', 'code'] + ['testing', 'code', 'test_excinfo.py'] + ['testing', 'code', 'test_excinfo.py', 'TestFormattedExcinfo', '()'] + """ + if nodeid == '': + # If there is no root node at all, return an empty list so the caller's logic can remain sane + return [] + parts = nodeid.split(SEP) + # Replace single last element 'test_foo.py::Bar::()' with multiple elements 'test_foo.py', 'Bar', '()' + parts[-1:] = parts[-1].split("::") + return parts + + +def ischildnode(baseid, nodeid): + """Return True if the nodeid is a child node of the baseid. + + E.g. 'foo/bar::Baz::()' is a child of 'foo', 'foo/bar' and 'foo/bar::Baz', but not of 'foo/blorp' + """ + base_parts = _splitnode(baseid) + node_parts = _splitnode(nodeid) + if len(node_parts) < len(base_parts): + return False + return node_parts[:len(base_parts)] == base_parts diff --git a/_pytest/pytester.py b/_pytest/pytester.py index 263f29e88..82aa00e0d 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -182,9 +182,9 @@ class PytestArg: return hookrecorder -def get_public_names(l): - """Only return names from iterator l without a leading underscore.""" - return [x for x in l if x[0] != "_"] +def get_public_names(values): + """Only return names from iterator values without a leading underscore.""" + return [x for x in values if x[0] != "_"] class ParsedCall: @@ -258,9 +258,9 @@ class HookRecorder: pytest.fail("\n".join(lines)) def getcall(self, name): - l = self.getcalls(name) - assert len(l) == 1, (name, l) - return l[0] + values = self.getcalls(name) + assert len(values) == 1, (name, values) + return values[0] # functionality for test reports @@ -271,7 +271,7 @@ class HookRecorder: def matchreport(self, inamepart="", names="pytest_runtest_logreport pytest_collectreport", when=None): """ return a testreport whose dotted import path matches """ - l = [] + values = [] for rep in self.getreports(names=names): try: if not when and rep.when != "call" and rep.passed: @@ -282,14 +282,14 @@ class HookRecorder: if when and getattr(rep, 'when', None) != when: continue if not inamepart or inamepart in rep.nodeid.split("::"): - l.append(rep) - if not l: + values.append(rep) + if not values: raise ValueError("could not find test report matching %r: " "no test reports at all!" % (inamepart,)) - if len(l) > 1: + if len(values) > 1: raise ValueError( - "found 2 or more testreports matching %r: %s" % (inamepart, l)) - return l[0] + "found 2 or more testreports matching %r: %s" % (inamepart, values)) + return values[0] def getfailures(self, names='pytest_runtest_logreport pytest_collectreport'): @@ -673,8 +673,8 @@ class Testdir: """ p = self.makepyfile(source) - l = list(cmdlineargs) + [p] - return self.inline_run(*l) + values = list(cmdlineargs) + [p] + return self.inline_run(*values) def inline_genitems(self, *args): """Run ``pytest.main(['--collectonly'])`` in-process. diff --git a/_pytest/python.py b/_pytest/python.py index e7bb9ad62..41fd2bdb7 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -321,7 +321,7 @@ class PyCollector(PyobjMixin, main.Collector): for basecls in inspect.getmro(self.obj.__class__): dicts.append(basecls.__dict__) seen = {} - l = [] + values = [] for dic in dicts: for name, obj in list(dic.items()): if name in seen: @@ -332,9 +332,9 @@ class PyCollector(PyobjMixin, main.Collector): continue if not isinstance(res, list): res = [res] - l.extend(res) - l.sort(key=lambda item: item.reportinfo()[:2]) - return l + values.extend(res) + values.sort(key=lambda item: item.reportinfo()[:2]) + return values def makeitem(self, name, obj): # assert self.ihook.fspath == self.fspath, self @@ -592,7 +592,7 @@ class Generator(FunctionMixin, PyCollector): self.session._setupstate.prepare(self) # see FunctionMixin.setup and test_setupstate_is_preserved_134 self._preservedparent = self.parent.obj - l = [] + values = [] seen = {} for i, x in enumerate(self.obj()): name, call, args = self.getcallargs(x) @@ -605,9 +605,9 @@ class Generator(FunctionMixin, PyCollector): if name in seen: raise ValueError("%r generated tests with non-unique name %r" % (self, name)) seen[name] = True - l.append(self.Function(name, self, args=args, callobj=call)) + values.append(self.Function(name, self, args=args, callobj=call)) self.warn('C1', deprecated.YIELD_TESTS) - return l + return values def getcallargs(self, obj): if not isinstance(obj, (tuple, list)): @@ -979,50 +979,48 @@ def _show_fixtures_per_test(config, session): tw = _pytest.config.create_terminal_writer(config) verbose = config.getvalue("verbose") - def get_best_rel(func): + def get_best_relpath(func): loc = getlocation(func, curdir) return curdir.bestrelpath(loc) def write_fixture(fixture_def): argname = fixture_def.argname - if verbose <= 0 and argname.startswith("_"): return if verbose > 0: - bestrel = get_best_rel(fixture_def.func) + bestrel = get_best_relpath(fixture_def.func) funcargspec = "{0} -- {1}".format(argname, bestrel) else: funcargspec = argname tw.line(funcargspec, green=True) - fixture_doc = fixture_def.func.__doc__ - if fixture_doc: write_docstring(tw, fixture_doc) else: tw.line(' no docstring available', red=True) def write_item(item): - name2fixturedefs = item._fixtureinfo.name2fixturedefs - - if not name2fixturedefs: - # The given test item does not use any fixtures + try: + info = item._fixtureinfo + except AttributeError: + # doctests items have no _fixtureinfo attribute + return + if not info.name2fixturedefs: + # this test item does not use any fixtures return - bestrel = get_best_rel(item.function) - tw.line() tw.sep('-', 'fixtures used by {0}'.format(item.name)) - tw.sep('-', '({0})'.format(bestrel)) - for argname, fixture_defs in sorted(name2fixturedefs.items()): - assert fixture_defs is not None - if not fixture_defs: + tw.sep('-', '({0})'.format(get_best_relpath(item.function))) + # dict key not used in loop but needed for sorting + for _, fixturedefs in sorted(info.name2fixturedefs.items()): + assert fixturedefs is not None + if not fixturedefs: continue - # The last fixture def item in the list is expected - # to be the one used by the test item - write_fixture(fixture_defs[-1]) + # last item is expected to be the one used by the test item + write_fixture(fixturedefs[-1]) - for item in session.items: - write_item(item) + for session_item in session.items: + write_item(session_item) def showfixtures(config): diff --git a/_pytest/python_api.py b/_pytest/python_api.py index b73e0457c..80684c131 100644 --- a/_pytest/python_api.py +++ b/_pytest/python_api.py @@ -84,7 +84,7 @@ class ApproxNumpy(ApproxBase): try: actual = np.asarray(actual) - except: + except: # noqa raise TypeError("cannot compare '{0}' to numpy.ndarray".format(actual)) if actual.shape != self.expected.shape: @@ -217,7 +217,8 @@ class ApproxScalar(ApproxBase): absolute tolerance or a relative tolerance, depending on what the user specified or which would be larger. """ - def set_default(x, default): return x if x is not None else default + def set_default(x, default): + return x if x is not None else default # Figure out what the absolute tolerance should be. ``self.abs`` is # either None or a value specified by the user. diff --git a/_pytest/runner.py b/_pytest/runner.py index ed58aceec..b643fa3c9 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -56,11 +56,6 @@ def pytest_sessionfinish(session): session._setupstate.teardown_all() -class NodeInfo: - def __init__(self, location): - self.location = location - - def pytest_runtest_protocol(item, nextitem): item.ihook.pytest_runtest_logstart( nodeid=item.nodeid, location=item.location, @@ -197,7 +192,7 @@ class CallInfo: except KeyboardInterrupt: self.stop = time() raise - except: + except: # noqa self.excinfo = ExceptionInfo() self.stop = time() diff --git a/_pytest/skipping.py b/_pytest/skipping.py index 2fd61448a..b92800d10 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -346,10 +346,10 @@ def folded_skips(skipped): key = event.longrepr assert len(key) == 3, (event, key) d.setdefault(key, []).append(event) - l = [] + values = [] for key, events in d.items(): - l.append((len(events),) + key) - return l + values.append((len(events),) + key) + return values def show_skipped(terminalreporter, lines): diff --git a/_pytest/terminal.py b/_pytest/terminal.py index bb114391d..9da94d0c9 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -13,6 +13,7 @@ import sys import time import platform +from _pytest import nodes import _pytest._pluggy as pluggy @@ -444,15 +445,15 @@ class TerminalReporter: line = self.config.cwd_relative_nodeid(nodeid) if domain and line.endswith(domain): line = line[:-len(domain)] - l = domain.split("[") - l[0] = l[0].replace('.', '::') # don't replace '.' in params - line += "[".join(l) + values = domain.split("[") + values[0] = values[0].replace('.', '::') # don't replace '.' in params + line += "[".join(values) return line # collect_fspath comes from testid which has a "/"-normalized path if fspath: res = mkrel(nodeid).replace("::()", "") # parens-normalization - if nodeid.split("::")[0] != fspath.replace("\\", "/"): + if nodeid.split("::")[0] != fspath.replace("\\", nodes.SEP): res += " <- " + self.startdir.bestrelpath(fspath) else: res = "[location]" @@ -478,11 +479,11 @@ class TerminalReporter: # summaries for sessionfinish # def getreports(self, name): - l = [] + values = [] for x in self.stats.get(name, []): if not hasattr(x, '_pdbshown'): - l.append(x) - return l + values.append(x) + return values def summary_warnings(self): if self.hasopt("w"): @@ -593,8 +594,8 @@ def repr_pythonversion(v=None): return str(v) -def flatten(l): - for x in l: +def flatten(values): + for x in values: if isinstance(x, (list, tuple)): for y in flatten(x): yield y @@ -635,7 +636,7 @@ def build_summary_stats_line(stats): def _plugin_nameversions(plugininfo): - l = [] + values = [] for plugin, dist in plugininfo: # gets us name and version! name = '{dist.project_name}-{dist.version}'.format(dist=dist) @@ -644,6 +645,6 @@ def _plugin_nameversions(plugininfo): name = name[7:] # we decided to print python package names # they can have more than one plugin - if name not in l: - l.append(name) - return l + if name not in values: + values.append(name) + return values diff --git a/_pytest/unittest.py b/_pytest/unittest.py index 585f81472..52c9813e8 100644 --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -109,13 +109,13 @@ class TestCaseFunction(Function): except TypeError: try: try: - l = traceback.format_exception(*rawexcinfo) - l.insert(0, "NOTE: Incompatible Exception Representation, " - "displaying natively:\n\n") - fail("".join(l), pytrace=False) + values = traceback.format_exception(*rawexcinfo) + values.insert(0, "NOTE: Incompatible Exception Representation, " + "displaying natively:\n\n") + fail("".join(values), pytrace=False) except (fail.Exception, KeyboardInterrupt): raise - except: + except: # noqa fail("ERROR: Unknown Incompatible Exception " "representation:\n%r" % (rawexcinfo,), pytrace=False) except KeyboardInterrupt: diff --git a/changelog/1505.doc b/changelog/1505.doc new file mode 100644 index 000000000..1b303d1bd --- /dev/null +++ b/changelog/1505.doc @@ -0,0 +1 @@ +Introduce a dedicated section about conftest.py. diff --git a/changelog/1548.doc b/changelog/1548.doc deleted file mode 100644 index 84ad8f10c..000000000 --- a/changelog/1548.doc +++ /dev/null @@ -1 +0,0 @@ -Add note in ``parametrize.rst`` about calling ``metafunc.parametrize`` multiple times. \ No newline at end of file diff --git a/changelog/1997.doc b/changelog/1997.doc new file mode 100644 index 000000000..0fa110dc9 --- /dev/null +++ b/changelog/1997.doc @@ -0,0 +1 @@ +Explicitly mention ``xpass`` in the documentation of ``xfail``. diff --git a/changelog/2658.doc b/changelog/2658.doc new file mode 100644 index 000000000..2da7f3d6c --- /dev/null +++ b/changelog/2658.doc @@ -0,0 +1 @@ +Append example for pytest.param in the example/parametrize document. \ No newline at end of file diff --git a/changelog/2722.trivial b/changelog/2722.trivial deleted file mode 100644 index 2c0ccd7b1..000000000 --- a/changelog/2722.trivial +++ /dev/null @@ -1 +0,0 @@ -Set ``xfail_strict=True`` in pytest's own test suite to catch expected failures as soon as they start to pass. diff --git a/changelog/2758.bugfix b/changelog/2758.bugfix deleted file mode 100644 index 12e4046b8..000000000 --- a/changelog/2758.bugfix +++ /dev/null @@ -1 +0,0 @@ -The equality checking function (``__eq__``) of ``MarkDecorator`` returns ``False`` if one object is not an instance of ``MarkDecorator``. \ No newline at end of file diff --git a/changelog/2765.trivial b/changelog/2765.trivial deleted file mode 100644 index 01110b852..000000000 --- a/changelog/2765.trivial +++ /dev/null @@ -1 +0,0 @@ -Fix typo in example of passing a callable to markers (in example/markers.rst) \ No newline at end of file diff --git a/changelog/2819.bugfix b/changelog/2819.bugfix new file mode 100644 index 000000000..303903cf7 --- /dev/null +++ b/changelog/2819.bugfix @@ -0,0 +1 @@ +Fix issue with @pytest.parametrize if argnames was specified as kwarg. \ No newline at end of file diff --git a/changelog/2836.bug b/changelog/2836.bug new file mode 100644 index 000000000..afa1961d7 --- /dev/null +++ b/changelog/2836.bug @@ -0,0 +1 @@ +Match fixture paths against actual path segments in order to avoid matching folders which share a prefix. diff --git a/changelog/2856.bugfix b/changelog/2856.bugfix new file mode 100644 index 000000000..7e5fc8fc7 --- /dev/null +++ b/changelog/2856.bugfix @@ -0,0 +1 @@ +Strip whitespace from marker names when reading them from INI config. diff --git a/changelog/2882.bugfix b/changelog/2882.bugfix new file mode 100644 index 000000000..2bda24c01 --- /dev/null +++ b/changelog/2882.bugfix @@ -0,0 +1 @@ +Show full context of doctest source in the pytest output, if the lineno of failed example in the docstring is < 9. \ No newline at end of file diff --git a/changelog/2893.doc b/changelog/2893.doc new file mode 100644 index 000000000..a305f1890 --- /dev/null +++ b/changelog/2893.doc @@ -0,0 +1 @@ +Clarify language of proposal for fixtures parameters diff --git a/changelog/2903.doc b/changelog/2903.doc new file mode 100644 index 000000000..492a8c685 --- /dev/null +++ b/changelog/2903.doc @@ -0,0 +1 @@ +List python 3.6 in the documented supported versions in the getting started document. diff --git a/changelog/538.doc b/changelog/538.doc new file mode 100644 index 000000000..bc5fb712f --- /dev/null +++ b/changelog/538.doc @@ -0,0 +1 @@ +Clarify the documentation of available fixture scopes. diff --git a/changelog/911.doc b/changelog/911.doc new file mode 100644 index 000000000..e9d94f21c --- /dev/null +++ b/changelog/911.doc @@ -0,0 +1 @@ +Add documentation about the ``python -m pytest`` invocation adding the current directory to sys.path. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index a822adda5..58b9aeec7 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-3.2.3 release-3.2.2 release-3.2.1 release-3.2.0 diff --git a/doc/en/announce/release-3.2.3.rst b/doc/en/announce/release-3.2.3.rst new file mode 100644 index 000000000..589374974 --- /dev/null +++ b/doc/en/announce/release-3.2.3.rst @@ -0,0 +1,23 @@ +pytest-3.2.3 +======================================= + +pytest 3.2.3 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Bruno Oliveira +* Evan +* Joe Hamman +* Oliver Bestwalter +* Ronny Pfannschmidt +* Xuan Luong + + +Happy testing, +The pytest Development Team diff --git a/doc/en/assert.rst b/doc/en/assert.rst index a8ddaecd8..d9e044356 100644 --- a/doc/en/assert.rst +++ b/doc/en/assert.rst @@ -209,8 +209,8 @@ the ``pytest_assertrepr_compare`` hook. .. autofunction:: _pytest.hookspec.pytest_assertrepr_compare :noindex: -As an example consider adding the following hook in a conftest.py which -provides an alternative explanation for ``Foo`` objects:: +As an example consider adding the following hook in a :ref:`conftest.py ` +file which provides an alternative explanation for ``Foo`` objects:: # content of conftest.py from test_foocompare import Foo diff --git a/doc/en/contents.rst b/doc/en/contents.rst index 028414eb6..6b9eed010 100644 --- a/doc/en/contents.rst +++ b/doc/en/contents.rst @@ -41,6 +41,7 @@ Full pytest documentation historical-notes license contributing + development_guide talks projects faq diff --git a/doc/en/development_guide.rst b/doc/en/development_guide.rst new file mode 100644 index 000000000..465e97de0 --- /dev/null +++ b/doc/en/development_guide.rst @@ -0,0 +1,108 @@ +================= +Development Guide +================= + +Some general guidelines regarding development in pytest for core maintainers and general contributors. Nothing here +is set in stone and can't be changed, feel free to suggest improvements or changes in the workflow. + + +Code Style +---------- + +* `PEP-8 `_ +* `flake8 `_ for quality checks +* `invoke `_ to automate development tasks + + +Branches +-------- + +We have two long term branches: + +* ``master``: contains the code for the next bugfix release. +* ``features``: contains the code with new features for the next minor release. + +The official repository usually does not contain topic branches, developers and contributors should create topic +branches in their own forks. + +Exceptions can be made for cases where more than one contributor is working on the same +topic or where it makes sense to use some automatic capability of the main repository, such as automatic docs from +`readthedocs `_ for a branch dealing with documentation refactoring. + +Issues +------ + +Any question, feature, bug or proposal is welcome as an issue. Users are encouraged to use them whenever they need. + +GitHub issues should use labels to categorize them. Labels should be created sporadically, to fill a niche; we should +avoid creating labels just for the sake of creating them. + +Here is a list of labels and a brief description mentioning their intent. + + +**Type** + +* ``type: backward compatibility``: issue that will cause problems with old pytest versions. +* ``type: bug``: problem that needs to be addressed. +* ``type: deprecation``: feature that will be deprecated in the future. +* ``type: docs``: documentation missing or needing clarification. +* ``type: enhancement``: new feature or API change, should be merged into ``features``. +* ``type: feature-branch``: new feature or API change, should be merged into ``features``. +* ``type: infrastructure``: improvement to development/releases/CI structure. +* ``type: performance``: performance or memory problem/improvement. +* ``type: proposal``: proposal for a new feature, often to gather opinions or design the API around the new feature. +* ``type: question``: question regarding usage, installation, internals or how to test something. +* ``type: refactoring``: internal improvements to the code. +* ``type: regression``: indicates a problem that was introduced in a release which was working previously. + +**Status** + +* ``status: critical``: grave problem or usability issue that affects lots of users. +* ``status: easy``: easy issue that is friendly to new contributors. +* ``status: help wanted``: core developers need help from experts on this topic. +* ``status: needs information``: reporter needs to provide more information; can be closed after 2 or more weeks of inactivity. + +**Topic** + +* ``topic: collection`` +* ``topic: fixtures`` +* ``topic: parametrize`` +* ``topic: reporting`` +* ``topic: selection`` +* ``topic: tracebacks`` + +**Plugin (internal or external)** + +* ``plugin: cache`` +* ``plugin: capture`` +* ``plugin: doctests`` +* ``plugin: junitxml`` +* ``plugin: monkeypatch`` +* ``plugin: nose`` +* ``plugin: pastebin`` +* ``plugin: pytester`` +* ``plugin: tmpdir`` +* ``plugin: unittest`` +* ``plugin: warnings`` +* ``plugin: xdist`` + + +**OS** + +Issues specific to a single operating system. Do not use as a means to indicate where an issue originated from, only +for problems that happen **only** in that system. + +* ``os: linux`` +* ``os: mac`` +* ``os: windows`` + +**Temporary** + +Used to classify issues for limited time, to help find issues related in events for example. +They should be removed after they are no longer relevant. + +* ``temporary: EP2017 sprint``: +* ``temporary: sprint-candidate``: + + +.. include:: ../../HOWTORELEASE.rst diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index b72e8e6de..1a8de235a 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -350,7 +350,7 @@ Parametrizing test methods through per-class configuration .. _`unittest parametrizer`: https://github.com/testing-cabal/unittest-ext/blob/master/params.py -Here is an example ``pytest_generate_function`` function implementing a +Here is an example ``pytest_generate_tests`` function implementing a parametrization scheme similar to Michael Foord's `unittest parametrizer`_ but in a lot less code:: @@ -485,4 +485,54 @@ of our ``test_func1`` was skipped. A few notes: values as well. +Set marks or test ID for individual parametrized test +-------------------------------------------------------------------- +Use ``pytest.param`` to apply marks or set test ID to individual parametrized test. +For example:: + + # content of test_pytest_param_example.py + import pytest + @pytest.mark.parametrize('test_input,expected', [ + ('3+5', 8), + pytest.param('1+7', 8, + marks=pytest.mark.basic), + pytest.param('2+4', 6, + marks=pytest.mark.basic, + id='basic_2+4'), + pytest.param('6*9', 42, + marks=[pytest.mark.basic, pytest.mark.xfail], + id='basic_6*9'), + ]) + def test_eval(test_input, expected): + assert eval(test_input) == expected + +In this example, we have 4 parametrized tests. Except for the first test, +we mark the rest three parametrized tests with the custom marker ``basic``, +and for the fourth test we also use the built-in mark ``xfail`` to indicate this +test is expected to fail. For explicitness, we set test ids for some tests. + +Then run ``pytest`` with verbose mode and with only the ``basic`` marker:: + + pytest -v -m basic + ============================================ test session starts ============================================= + platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y + rootdir: $REGENDOC_TMPDIR, inifile: + collected 4 items + + test_pytest_param_example.py::test_eval[1+7-8] PASSED + test_pytest_param_example.py::test_eval[basic_2+4] PASSED + test_pytest_param_example.py::test_eval[basic_6*9] xfail + ========================================== short test summary info =========================================== + XFAIL test_pytest_param_example.py::test_eval[basic_6*9] + + ============================================= 1 tests deselected ============================================= + +As the result: + +- Four tests were collected +- One test was deselected because it doesn't have the ``basic`` mark. +- Three tests with the ``basic`` mark was selected. +- The test ``test_eval[1+7-8]`` passed, but the name is autogenerated and confusing. +- The test ``test_eval[basic_2+4]`` passed. +- The test ``test_eval[basic_6*9]`` was expected to fail and did fail. diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 8d36c2e37..5fb63035a 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -175,21 +175,23 @@ You can always peek at the collection tree without running tests like this:: ======= no tests ran in 0.12 seconds ======== -customizing test collection to find all .py files ---------------------------------------------------------- +.. _customizing-test-collection: + +Customizing test collection +--------------------------- .. regendoc:wipe -You can easily instruct ``pytest`` to discover tests from every python file:: - +You can easily instruct ``pytest`` to discover tests from every Python file:: # content of pytest.ini [pytest] python_files = *.py -However, many projects will have a ``setup.py`` which they don't want to be imported. Moreover, there may files only importable by a specific python version. -For such cases you can dynamically define files to be ignored by listing -them in a ``conftest.py`` file:: +However, many projects will have a ``setup.py`` which they don't want to be +imported. Moreover, there may files only importable by a specific python +version. For such cases you can dynamically define files to be ignored by +listing them in a ``conftest.py`` file:: # content of conftest.py import sys @@ -198,7 +200,7 @@ them in a ``conftest.py`` file:: if sys.version_info[0] > 2: collect_ignore.append("pkg/module_py2.py") -And then if you have a module file like this:: +and then if you have a module file like this:: # content of pkg/module_py2.py def test_only_on_python2(): @@ -207,13 +209,13 @@ And then if you have a module file like this:: except Exception, e: pass -and a setup.py dummy file like this:: +and a ``setup.py`` dummy file like this:: # content of setup.py 0/0 # will raise exception if imported -then a pytest run on Python2 will find the one test and will leave out the -setup.py file:: +If you run with a Python 2 interpreter then you will find the one test and will +leave out the ``setup.py`` file:: #$ pytest --collect-only ====== test session starts ====== @@ -225,13 +227,13 @@ setup.py file:: ====== no tests ran in 0.04 seconds ====== -If you run with a Python3 interpreter both the one test and the setup.py file -will be left out:: +If you run with a Python 3 interpreter both the one test and the ``setup.py`` +file will be left out:: $ pytest --collect-only ======= test session starts ======== platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 0 items - + ======= no tests ran in 0.12 seconds ======== diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index db7ef0ca2..1d7ba8640 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -27,7 +27,7 @@ functions: * fixture management scales from simple unit to complex functional testing, allowing to parametrize fixtures and tests according to configuration and component options, or to re-use fixtures - across class, module or whole test session scopes. + across function, class, module or whole test session scopes. In addition, pytest continues to support :ref:`xunitsetup`. You can mix both styles, moving incrementally from classic to new style, as you @@ -127,10 +127,39 @@ It's a prime example of `dependency injection`_ where fixture functions take the role of the *injector* and test functions are the *consumers* of fixture objects. +.. _`conftest.py`: +.. _`conftest`: + +``conftest.py``: sharing fixture functions +------------------------------------------ + +If during implementing your tests you realize that you +want to use a fixture function from multiple test files you can move it +to a ``conftest.py`` file. +You don't need to import the fixture you want to use in a test, it +automatically gets discovered by pytest. The discovery of +fixture functions starts at test classes, then test modules, then +``conftest.py`` files and finally builtin and third party plugins. + +You can also use the ``conftest.py`` file to implement +:ref:`local per-directory plugins `. + +Sharing test data +----------------- + +If you want to make test data from files available to your tests, a good way +to do this is by loading these data in a fixture for use by your tests. +This makes use of the automatic caching mechanisms of pytest. + +Another good approach is by adding the data files in the ``tests`` folder. +There are also community plugins available to help managing this aspect of +testing, e.g. `pytest-datadir `__ +and `pytest-datafiles `__. + .. _smtpshared: -Sharing a fixture across tests in a module (or class/session) ------------------------------------------------------------------ +Scope: sharing a fixture instance across tests in a class, module or session +---------------------------------------------------------------------------- .. regendoc:wipe @@ -139,10 +168,12 @@ usually time-expensive to create. Extending the previous example, we can add a ``scope='module'`` parameter to the :py:func:`@pytest.fixture <_pytest.python.fixture>` invocation to cause the decorated ``smtp`` fixture function to only be invoked once -per test module. Multiple test functions in a test module will thus -each receive the same ``smtp`` fixture instance. The next example puts -the fixture function into a separate ``conftest.py`` file so -that tests from multiple test modules in the directory can +per test *module* (the default is to invoke once per test *function*). +Multiple test functions in a test module will thus +each receive the same ``smtp`` fixture instance, thus saving time. + +The next example puts the fixture function into a separate ``conftest.py`` file +so that tests from multiple test modules in the directory can access the fixture function:: # content of conftest.py @@ -223,6 +254,8 @@ instance, you can simply declare it: # the returned fixture value will be shared for # all tests needing it +Finally, the ``class`` scope will invoke the fixture once per test *class*. + .. _`finalization`: Fixture finalization / executing teardown code @@ -858,7 +891,7 @@ into a conftest.py file **without** using ``autouse``:: # content of conftest.py @pytest.fixture - def transact(self, request, db): + def transact(request, db): db.begin() yield db.rollback() @@ -874,17 +907,6 @@ All test methods in this TestClass will use the transaction fixture while other test classes or functions in the module will not use it unless they also add a ``transact`` reference. - -Shifting (visibility of) fixture functions ----------------------------------------------------- - -If during implementing your tests you realize that you -want to use a fixture function from multiple test files you can move it -to a :ref:`conftest.py ` file or even separately installable -:ref:`plugins ` without changing test code. The discovery of -fixtures functions starts at test classes, then test modules, then -``conftest.py`` files and finally builtin and third party plugins. - Overriding fixtures on various levels ------------------------------------- diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 1571e4f6b..0b336a41f 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -1,7 +1,7 @@ Installation and Getting Started =================================== -**Pythons**: Python 2.6,2.7,3.3,3.4,3.5, Jython, PyPy-2.3 +**Pythons**: Python 2.6,2.7,3.3,3.4,3.5,3.6 Jython, PyPy-2.3 **Platforms**: Unix/Posix and Windows diff --git a/doc/en/parametrize.rst b/doc/en/parametrize.rst index 6215cf133..ebbae31b2 100644 --- a/doc/en/parametrize.rst +++ b/doc/en/parametrize.rst @@ -198,10 +198,12 @@ list:: SKIP [1] test_strings.py:2: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1 1 skipped in 0.12 seconds - Note that when calling ``metafunc.parametrize`` multiple times with different parameter sets, all parameter names across those sets cannot be duplicated, otherwise an error will be raised. +More examples +------------- + For further examples, you might want to look at :ref:`more parametrization examples `. diff --git a/doc/en/plugins.rst b/doc/en/plugins.rst index ec031e9e0..4a6772ca3 100644 --- a/doc/en/plugins.rst +++ b/doc/en/plugins.rst @@ -94,7 +94,7 @@ environment you can type:: and will get an extended test header which shows activated plugins and their names. It will also print local plugins aka -:ref:`conftest.py ` files when they are loaded. +:ref:`conftest.py ` files when they are loaded. .. _`cmdunregister`: @@ -155,4 +155,3 @@ in the `pytest repository `_. _pytest.terminal _pytest.tmpdir _pytest.unittest - diff --git a/doc/en/proposals/parametrize_with_fixtures.rst b/doc/en/proposals/parametrize_with_fixtures.rst index 381bc98f1..146032aa4 100644 --- a/doc/en/proposals/parametrize_with_fixtures.rst +++ b/doc/en/proposals/parametrize_with_fixtures.rst @@ -1,8 +1,13 @@ :orphan: -========================= -Parametrize with fixtures -========================= +=================================== +PROPOSAL: Parametrize with fixtures +=================================== + +.. warning:: + + This document outlines a proposal around using fixtures as input + of parametrized tests or fixtures. Problem ------- @@ -108,8 +113,13 @@ the following values. Alternative approach -------------------- -A new helper function named ``fixture_request`` tells pytest to yield all -parameters of a fixture. +A new helper function named ``fixture_request`` would tell pytest to yield +all parameters marked as a fixture. + +.. note:: + + The `pytest-lazy-fixture `_ plugin implements a very + similar solution to the proposal below, make sure to check it out. .. code-block:: python diff --git a/doc/en/pythonpath.rst b/doc/en/pythonpath.rst index 67de7f5d2..b64742768 100644 --- a/doc/en/pythonpath.rst +++ b/doc/en/pythonpath.rst @@ -68,4 +68,9 @@ imported in the global import namespace. This is also discussed in details in :ref:`test discovery`. +Invoking ``pytest`` versus ``python -m pytest`` +----------------------------------------------- +Running pytest with ``python -m pytest [...]`` instead of ``pytest [...]`` yields nearly +equivalent behaviour, except that the former call will add the current directory to ``sys.path``. +See also :ref:`cmdline`. diff --git a/doc/en/skipping.rst b/doc/en/skipping.rst index 630f73422..d6d1e2414 100644 --- a/doc/en/skipping.rst +++ b/doc/en/skipping.rst @@ -3,7 +3,7 @@ .. _skipping: Skip and xfail: dealing with tests that cannot succeed -===================================================================== +====================================================== You can mark test functions that cannot be run on certain platforms or that you expect to fail so pytest can deal with them accordingly and @@ -16,13 +16,17 @@ resource which is not available at the moment (for example a database). A **xfail** means that you expect a test to fail for some reason. A common example is a test for a feature not yet implemented, or a bug not yet fixed. +When a test passes despite being expected to fail (marked with ``pytest.mark.xfail``), +it's an **xpass** and will be reported in the test summary. ``pytest`` counts and lists *skip* and *xfail* tests separately. Detailed information about skipped/xfailed tests is not shown by default to avoid cluttering the output. You can use the ``-r`` option to see details corresponding to the "short" letters shown in the test progress:: - pytest -rxs # show extra info on skips and xfails + pytest -rxXs # show extra info on xfailed, xpassed, and skipped tests + +More details on the ``-r`` option can be found by running ``pytest -h``. (See :ref:`how to change command line options defaults`) @@ -138,6 +142,16 @@ will be skipped if any of the skip conditions is true. .. _`whole class- or module level`: mark.html#scoped-marking +Skipping files or directories +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes you may need to skip an entire file or directory, for example if the +tests rely on Python version-specific features or contain code that you do not +wish pytest to run. In this case, you must exclude the files and directories +from collection. Refer to :ref:`customizing-test-collection` for more +information. + + Skipping on a missing import dependency ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/en/unittest.rst b/doc/en/unittest.rst index 92b9e653e..db1692029 100644 --- a/doc/en/unittest.rst +++ b/doc/en/unittest.rst @@ -233,3 +233,13 @@ was executed ahead of the ``test_method``. overwrite ``unittest.TestCase`` ``__call__`` or ``run``, they need to to overwrite ``debug`` in the same way (this is also true for standard unittest). + +.. note:: + + Due to architectural differences between the two frameworks, setup and + teardown for ``unittest``-based tests is performed during the ``call`` phase + of testing instead of in ``pytest``'s standard ``setup`` and ``teardown`` + stages. This can be important to understand in some situations, particularly + when reasoning about errors. For example, if a ``unittest``-based suite + exhibits errors during setup, ``pytest`` will report no errors during its + ``setup`` phase and will instead raise the error during ``call``. diff --git a/doc/en/usage.rst b/doc/en/usage.rst index a8c6d40a0..c5b919fe9 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -17,7 +17,7 @@ You can invoke testing through the Python interpreter from the command line:: python -m pytest [...] This is almost equivalent to invoking the command line script ``pytest [...]`` -directly, except that Python will also add the current directory to ``sys.path``. +directly, except that calling via ``python`` will also add the current directory to ``sys.path``. Possible exit codes -------------------------------------------------------------- diff --git a/doc/en/warnings.rst b/doc/en/warnings.rst index c84277173..de8456af0 100644 --- a/doc/en/warnings.rst +++ b/doc/en/warnings.rst @@ -109,7 +109,7 @@ decorator or to all tests in a module by setting the ``pytestmark`` variable: .. code-block:: python # turns all warnings into errors for this module - pytestmark = @pytest.mark.filterwarnings('error') + pytestmark = pytest.mark.filterwarnings('error') .. note:: diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index d5ad73b4b..5dccdb884 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -57,9 +57,7 @@ Plugin discovery order at tool startup .. _`pytest/plugin`: http://bitbucket.org/pytest-dev/pytest/src/tip/pytest/plugin/ .. _`conftest.py plugins`: -.. _`conftest.py`: .. _`localplugin`: -.. _`conftest`: .. _`local conftest plugins`: conftest.py: local per-directory plugins diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index f8f8a0365..263d053b5 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -77,8 +77,8 @@ def test_excinfo_getstatement(): linenumbers = [_pytest._code.getrawcode(f).co_firstlineno - 1 + 4, _pytest._code.getrawcode(f).co_firstlineno - 1 + 1, _pytest._code.getrawcode(g).co_firstlineno - 1 + 1, ] - l = list(excinfo.traceback) - foundlinenumbers = [x.lineno for x in l] + values = list(excinfo.traceback) + foundlinenumbers = [x.lineno for x in values] assert foundlinenumbers == linenumbers # for x in info: # print "%s:%d %s" %(x.path.relto(root), x.lineno, x.statement) @@ -244,7 +244,7 @@ class TestTraceback_f_g_h(object): def f(n): try: do_stuff() - except: + except: # noqa reraise_me() excinfo = pytest.raises(RuntimeError, f, 8) @@ -434,7 +434,7 @@ class TestFormattedExcinfo(object): exec(source.compile()) except KeyboardInterrupt: raise - except: + except: # noqa return _pytest._code.ExceptionInfo() assert 0, "did not raise" @@ -1217,7 +1217,7 @@ def test_exception_repr_extraction_error_on_recursion(): try: a(numpy_like()) - except: + except: # noqa from _pytest._code.code import ExceptionInfo from _pytest.pytester import LineMatcher exc_info = ExceptionInfo() @@ -1241,7 +1241,7 @@ def test_no_recursion_index_on_recursion_error(): return getattr(self, '_' + attr) RecursionDepthError().trigger - except: + except: # noqa from _pytest._code.code import ExceptionInfo exc_info = ExceptionInfo() if sys.version_info[:2] == (2, 6): diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 1d315aa9b..4f3796cb4 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -155,8 +155,8 @@ class TestAccesses(object): assert len(self.source) == 4 def test_iter(self): - l = [x for x in self.source] - assert len(l) == 4 + values = [x for x in self.source] + assert len(values) == 4 class TestSourceParsingAndCompiling(object): @@ -331,8 +331,8 @@ def test_getstartingblock_singleline(): x = A('x', 'y') - l = [i for i in x.source.lines if i.strip()] - assert len(l) == 1 + values = [i for i in x.source.lines if i.strip()] + assert len(values) == 1 def test_getline_finally(): diff --git a/testing/code/test_source_multiline_block.py b/testing/code/test_source_multiline_block.py index 4e8735d0c..b356d191f 100644 --- a/testing/code/test_source_multiline_block.py +++ b/testing/code/test_source_multiline_block.py @@ -22,5 +22,5 @@ def test_getstartingblock_multiline(): , 'z') - l = [i for i in x.source.lines if i.strip()] - assert len(l) == 4 + values = [i for i in x.source.lines if i.strip()] + assert len(values) == 4 diff --git a/testing/python/collect.py b/testing/python/collect.py index b24c0b2fd..7b361a89e 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -873,11 +873,11 @@ class TestConftestCustomization(object): def test_makeitem_non_underscore(self, testdir, monkeypatch): modcol = testdir.getmodulecol("def _hello(): pass") - l = [] + values = [] monkeypatch.setattr(pytest.Module, 'makeitem', - lambda self, name, obj: l.append(name)) - l = modcol.collect() - assert '_hello' not in l + lambda self, name, obj: values.append(name)) + values = modcol.collect() + assert '_hello' not in values def test_issue2369_collect_module_fileext(self, testdir): """Ensure we can collect files with weird file extensions as Python diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 06b08d68e..ac385c995 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -548,12 +548,12 @@ class TestRequestBasic(object): def test_getfixturevalue(self, testdir, getfixmethod): item = testdir.getitem(""" import pytest - l = [2] + values = [2] @pytest.fixture def something(request): return 1 @pytest.fixture def other(request): - return l.pop() + return values.pop() def test_func(something): pass """) import contextlib @@ -622,15 +622,15 @@ class TestRequestBasic(object): def test_request_addfinalizer_failing_setup(self, testdir): testdir.makepyfile(""" import pytest - l = [1] + values = [1] @pytest.fixture def myfix(request): - request.addfinalizer(l.pop) + request.addfinalizer(values.pop) assert 0 def test_fix(myfix): pass def test_finalizer_ran(): - assert not l + assert not values """) reprec = testdir.inline_run("-s") reprec.assertoutcome(failed=1, passed=1) @@ -638,30 +638,30 @@ class TestRequestBasic(object): def test_request_addfinalizer_failing_setup_module(self, testdir): testdir.makepyfile(""" import pytest - l = [1, 2] + values = [1, 2] @pytest.fixture(scope="module") def myfix(request): - request.addfinalizer(l.pop) - request.addfinalizer(l.pop) + request.addfinalizer(values.pop) + request.addfinalizer(values.pop) assert 0 def test_fix(myfix): pass """) reprec = testdir.inline_run("-s") mod = reprec.getcalls("pytest_runtest_setup")[0].item.module - assert not mod.l + assert not mod.values def test_request_addfinalizer_partial_setup_failure(self, testdir): p = testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture def something(request): - request.addfinalizer(lambda: l.append(None)) + request.addfinalizer(lambda: values.append(None)) def test_func(something, missingarg): pass def test_second(): - assert len(l) == 1 + assert len(values) == 1 """) result = testdir.runpytest(p) result.stdout.fnmatch_lines([ @@ -675,7 +675,7 @@ class TestRequestBasic(object): """ testdir.makepyfile(""" import pytest - l = [] + values = [] def _excepts(where): raise Exception('Error in %s fixture' % where) @pytest.fixture @@ -683,17 +683,17 @@ class TestRequestBasic(object): return request @pytest.fixture def something(subrequest): - subrequest.addfinalizer(lambda: l.append(1)) - subrequest.addfinalizer(lambda: l.append(2)) + subrequest.addfinalizer(lambda: values.append(1)) + subrequest.addfinalizer(lambda: values.append(2)) subrequest.addfinalizer(lambda: _excepts('something')) @pytest.fixture def excepts(subrequest): subrequest.addfinalizer(lambda: _excepts('excepts')) - subrequest.addfinalizer(lambda: l.append(3)) + subrequest.addfinalizer(lambda: values.append(3)) def test_first(something, excepts): pass def test_second(): - assert l == [3, 2, 1] + assert values == [3, 2, 1] """) result = testdir.runpytest() result.stdout.fnmatch_lines([ @@ -748,13 +748,13 @@ class TestRequestBasic(object): def test_setupdecorator_and_xunit(self, testdir): testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture(scope='module', autouse=True) def setup_module(): - l.append("module") + values.append("module") @pytest.fixture(autouse=True) def setup_function(): - l.append("function") + values.append("function") def test_func(): pass @@ -762,14 +762,14 @@ class TestRequestBasic(object): class TestClass(object): @pytest.fixture(scope="class", autouse=True) def setup_class(self): - l.append("class") + values.append("class") @pytest.fixture(autouse=True) def setup_method(self): - l.append("method") + values.append("method") def test_method(self): pass def test_all(): - assert l == ["module", "function", "class", + assert values == ["module", "function", "class", "function", "method", "function"] """) reprec = testdir.inline_run("-v") @@ -930,10 +930,10 @@ class TestRequestCachedSetup(object): def test_request_cachedsetup_extrakey(self, testdir): item1 = testdir.getitem("def test_func(): pass") req1 = fixtures.FixtureRequest(item1) - l = ["hello", "world"] + values = ["hello", "world"] def setup(): - return l.pop() + return values.pop() ret1 = req1.cached_setup(setup, extrakey=1) ret2 = req1.cached_setup(setup, extrakey=2) @@ -947,24 +947,24 @@ class TestRequestCachedSetup(object): def test_request_cachedsetup_cache_deletion(self, testdir): item1 = testdir.getitem("def test_func(): pass") req1 = fixtures.FixtureRequest(item1) - l = [] + values = [] def setup(): - l.append("setup") + values.append("setup") def teardown(val): - l.append("teardown") + values.append("teardown") req1.cached_setup(setup, teardown, scope="function") - assert l == ['setup'] + assert values == ['setup'] # artificial call of finalizer setupstate = req1._pyfuncitem.session._setupstate setupstate._callfinalizers(item1) - assert l == ["setup", "teardown"] + assert values == ["setup", "teardown"] req1.cached_setup(setup, teardown, scope="function") - assert l == ["setup", "teardown", "setup"] + assert values == ["setup", "teardown", "setup"] setupstate._callfinalizers(item1) - assert l == ["setup", "teardown", "setup", "teardown"] + assert values == ["setup", "teardown", "setup", "teardown"] def test_request_cached_setup_two_args(self, testdir): testdir.makepyfile(""" @@ -1006,17 +1006,17 @@ class TestRequestCachedSetup(object): def test_request_cached_setup_functional(self, testdir): testdir.makepyfile(test_0=""" import pytest - l = [] + values = [] @pytest.fixture def something(request): val = request.cached_setup(fsetup, fteardown) return val def fsetup(mycache=[1]): - l.append(mycache.pop()) - return l + values.append(mycache.pop()) + return values def fteardown(something): - l.remove(something[0]) - l.append(2) + values.remove(something[0]) + values.append(2) def test_list_once(something): assert something == [1] def test_list_twice(something): @@ -1025,7 +1025,7 @@ class TestRequestCachedSetup(object): testdir.makepyfile(test_1=""" import test_0 # should have run already def test_check_test0_has_teardown_correct(): - assert test_0.l == [2] + assert test_0.values == [2] """) result = testdir.runpytest("-v") result.stdout.fnmatch_lines([ @@ -1150,10 +1150,10 @@ class TestFixtureUsages(object): def test_funcarg_parametrized_and_used_twice(self, testdir): testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture(params=[1,2]) def arg1(request): - l.append(1) + values.append(1) return request.param @pytest.fixture() @@ -1162,7 +1162,7 @@ class TestFixtureUsages(object): def test_add(arg1, arg2): assert arg2 == arg1 + 1 - assert len(l) == arg1 + assert len(values) == arg1 """) result = testdir.runpytest() result.stdout.fnmatch_lines([ @@ -1203,8 +1203,8 @@ class TestFixtureUsages(object): """) reprec = testdir.inline_run() - l = reprec.getfailedcollections() - assert len(l) == 1 + values = reprec.getfailedcollections() + assert len(values) == 1 def test_request_can_be_overridden(self, testdir): testdir.makepyfile(""" @@ -1223,20 +1223,20 @@ class TestFixtureUsages(object): testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture(scope="class") def myfix(request): request.cls.hello = "world" - l.append(1) + values.append(1) class TestClass(object): def test_one(self): assert self.hello == "world" - assert len(l) == 1 + assert len(values) == 1 def test_two(self): assert self.hello == "world" - assert len(l) == 1 + assert len(values) == 1 pytest.mark.usefixtures("myfix")(TestClass) """) reprec = testdir.inline_run() @@ -1290,7 +1290,7 @@ class TestFixtureUsages(object): testdir.makepyfile(""" import pytest - l = [] + values = [] def f(): yield 1 yield 2 @@ -1304,14 +1304,14 @@ class TestFixtureUsages(object): return request.param def test_1(arg): - l.append(arg) + values.append(arg) def test_2(arg2): - l.append(arg2*10) + values.append(arg2*10) """) reprec = testdir.inline_run("-v") reprec.assertoutcome(passed=4) - l = reprec.getcalls("pytest_runtest_call")[0].item.module.l - assert l == [1, 2, 10, 20] + values = reprec.getcalls("pytest_runtest_call")[0].item.module.values + assert values == [1, 2, 10, 20] class TestFixtureManagerParseFactories(object): @@ -1461,19 +1461,19 @@ class TestAutouseDiscovery(object): testdir.makepyfile(""" import pytest class TestA(object): - l = [] + values = [] @pytest.fixture(autouse=True) def setup1(self): - self.l.append(1) + self.values.append(1) def test_setup1(self): - assert self.l == [1] + assert self.values == [1] class TestB(object): - l = [] + values = [] @pytest.fixture(autouse=True) def setup2(self): - self.l.append(1) + self.values.append(1) def test_setup2(self): - assert self.l == [1] + assert self.values == [1] """) reprec = testdir.inline_run() reprec.assertoutcome(passed=2) @@ -1556,22 +1556,22 @@ class TestAutouseDiscovery(object): def test_autouse_in_module_and_two_classes(self, testdir): testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture(autouse=True) def append1(): - l.append("module") + values.append("module") def test_x(): - assert l == ["module"] + assert values == ["module"] class TestA(object): @pytest.fixture(autouse=True) def append2(self): - l.append("A") + values.append("A") def test_hello(self): - assert l == ["module", "module", "A"], l + assert values == ["module", "module", "A"], values class TestA2(object): def test_world(self): - assert l == ["module", "module", "A", "module"], l + assert values == ["module", "module", "A", "module"], values """) reprec = testdir.inline_run() reprec.assertoutcome(passed=3) @@ -1615,23 +1615,23 @@ class TestAutouseManagement(object): def test_funcarg_and_setup(self, testdir): testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture(scope="module") def arg(): - l.append(1) + values.append(1) return 0 @pytest.fixture(scope="module", autouse=True) def something(arg): - l.append(2) + values.append(2) def test_hello(arg): - assert len(l) == 2 - assert l == [1,2] + assert len(values) == 2 + assert values == [1,2] assert arg == 0 def test_hello2(arg): - assert len(l) == 2 - assert l == [1,2] + assert len(values) == 2 + assert values == [1,2] assert arg == 0 """) reprec = testdir.inline_run() @@ -1640,20 +1640,20 @@ class TestAutouseManagement(object): def test_uses_parametrized_resource(self, testdir): testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture(params=[1,2]) def arg(request): return request.param @pytest.fixture(autouse=True) def something(arg): - l.append(arg) + values.append(arg) def test_hello(): - if len(l) == 1: - assert l == [1] - elif len(l) == 2: - assert l == [1, 2] + if len(values) == 1: + assert values == [1] + elif len(values) == 2: + assert values == [1, 2] else: 0/0 @@ -1665,7 +1665,7 @@ class TestAutouseManagement(object): testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture(scope="session", params=[1,2]) def arg(request): @@ -1674,14 +1674,14 @@ class TestAutouseManagement(object): @pytest.fixture(scope="function", autouse=True) def append(request, arg): if request.function.__name__ == "test_some": - l.append(arg) + values.append(arg) def test_some(): pass def test_result(arg): - assert len(l) == arg - assert l[:arg] == [1,2][:arg] + assert len(values) == arg + assert values[:arg] == [1,2][:arg] """) reprec = testdir.inline_run("-v", "-s") reprec.assertoutcome(passed=4) @@ -1691,7 +1691,7 @@ class TestAutouseManagement(object): import pytest import pprint - l = [] + values = [] @pytest.fixture(scope="function", params=[1,2]) def farg(request): @@ -1704,7 +1704,7 @@ class TestAutouseManagement(object): @pytest.fixture(scope="function", autouse=True) def append(request, farg, carg): def fin(): - l.append("fin_%s%s" % (carg, farg)) + values.append("fin_%s%s" % (carg, farg)) request.addfinalizer(fin) """) testdir.makepyfile(""" @@ -1721,26 +1721,26 @@ class TestAutouseManagement(object): reprec = testdir.inline_run("-v", "-s", confcut) reprec.assertoutcome(passed=8) config = reprec.getcalls("pytest_unconfigure")[0].config - l = config.pluginmanager._getconftestmodules(p)[0].l - assert l == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2 + values = config.pluginmanager._getconftestmodules(p)[0].values + assert values == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2 def test_scope_ordering(self, testdir): testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture(scope="function", autouse=True) def fappend2(): - l.append(2) + values.append(2) @pytest.fixture(scope="class", autouse=True) def classappend3(): - l.append(3) + values.append(3) @pytest.fixture(scope="module", autouse=True) def mappend(): - l.append(1) + values.append(1) class TestHallo(object): def test_method(self): - assert l == [1,3,2] + assert values == [1,3,2] """) reprec = testdir.inline_run() reprec.assertoutcome(passed=1) @@ -1748,23 +1748,23 @@ class TestAutouseManagement(object): def test_parametrization_setup_teardown_ordering(self, testdir): testdir.makepyfile(""" import pytest - l = [] + values = [] def pytest_generate_tests(metafunc): if metafunc.cls is not None: metafunc.parametrize("item", [1,2], scope="class") class TestClass(object): @pytest.fixture(scope="class", autouse=True) def addteardown(self, item, request): - l.append("setup-%d" % item) - request.addfinalizer(lambda: l.append("teardown-%d" % item)) + values.append("setup-%d" % item) + request.addfinalizer(lambda: values.append("teardown-%d" % item)) def test_step1(self, item): - l.append("step1-%d" % item) + values.append("step1-%d" % item) def test_step2(self, item): - l.append("step2-%d" % item) + values.append("step2-%d" % item) def test_finish(): - print (l) - assert l == ["setup-1", "step1-1", "step2-1", "teardown-1", + print (values) + assert values == ["setup-1", "step1-1", "step2-1", "teardown-1", "setup-2", "step1-2", "step2-2", "teardown-2",] """) reprec = testdir.inline_run() @@ -1774,15 +1774,15 @@ class TestAutouseManagement(object): testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture(autouse=True) def fix1(): - l.append(1) + values.append(1) @pytest.fixture() def arg1(): - l.append(2) + values.append(2) def test_hello(arg1): - assert l == [1,2] + assert values == [1,2] """) reprec = testdir.inline_run() reprec.assertoutcome(passed=1) @@ -1793,20 +1793,20 @@ class TestAutouseManagement(object): def test_ordering_dependencies_torndown_first(self, testdir, param1, param2): testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture(%(param1)s) def arg1(request): - request.addfinalizer(lambda: l.append("fin1")) - l.append("new1") + request.addfinalizer(lambda: values.append("fin1")) + values.append("new1") @pytest.fixture(%(param2)s) def arg2(request, arg1): - request.addfinalizer(lambda: l.append("fin2")) - l.append("new2") + request.addfinalizer(lambda: values.append("fin2")) + values.append("new2") def test_arg(arg2): pass def test_check(): - assert l == ["new1", "new2", "fin2", "fin1"] + assert values == ["new1", "new2", "fin2", "fin1"] """ % locals()) reprec = testdir.inline_run("-s") reprec.assertoutcome(passed=2) @@ -1819,11 +1819,11 @@ class TestFixtureMarker(object): @pytest.fixture(params=["a", "b", "c"]) def arg(request): return request.param - l = [] + values = [] def test_param(arg): - l.append(arg) + values.append(arg) def test_result(): - assert l == list("abc") + assert values == list("abc") """) reprec = testdir.inline_run() reprec.assertoutcome(passed=4) @@ -1867,21 +1867,21 @@ class TestFixtureMarker(object): def test_scope_session(self, testdir): testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture(scope="module") def arg(): - l.append(1) + values.append(1) return 1 def test_1(arg): assert arg == 1 def test_2(arg): assert arg == 1 - assert len(l) == 1 + assert len(values) == 1 class TestClass(object): def test3(self, arg): assert arg == 1 - assert len(l) == 1 + assert len(values) == 1 """) reprec = testdir.inline_run() reprec.assertoutcome(passed=3) @@ -1889,10 +1889,10 @@ class TestFixtureMarker(object): def test_scope_session_exc(self, testdir): testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture(scope="session") def fix(): - l.append(1) + values.append(1) pytest.skip('skipping') def test_1(fix): @@ -1900,7 +1900,7 @@ class TestFixtureMarker(object): def test_2(fix): pass def test_last(): - assert l == [1] + assert values == [1] """) reprec = testdir.inline_run() reprec.assertoutcome(skipped=2, passed=1) @@ -1908,11 +1908,11 @@ class TestFixtureMarker(object): def test_scope_session_exc_two_fix(self, testdir): testdir.makepyfile(""" import pytest - l = [] + values = [] m = [] @pytest.fixture(scope="session") def a(): - l.append(1) + values.append(1) pytest.skip('skipping') @pytest.fixture(scope="session") def b(a): @@ -1923,7 +1923,7 @@ class TestFixtureMarker(object): def test_2(b): pass def test_last(): - assert l == [1] + assert values == [1] assert m == [] """) reprec = testdir.inline_run() @@ -1961,21 +1961,21 @@ class TestFixtureMarker(object): def test_scope_module_uses_session(self, testdir): testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture(scope="module") def arg(): - l.append(1) + values.append(1) return 1 def test_1(arg): assert arg == 1 def test_2(arg): assert arg == 1 - assert len(l) == 1 + assert len(values) == 1 class TestClass(object): def test3(self, arg): assert arg == 1 - assert len(l) == 1 + assert len(values) == 1 """) reprec = testdir.inline_run() reprec.assertoutcome(passed=3) @@ -2070,17 +2070,17 @@ class TestFixtureMarker(object): @pytest.fixture(scope="module", params=["a", "b", "c"]) def arg(request): return request.param - l = [] + values = [] def test_param(arg): - l.append(arg) + values.append(arg) """) reprec = testdir.inline_run("-v") reprec.assertoutcome(passed=3) - l = reprec.getcalls("pytest_runtest_call")[0].item.module.l - assert len(l) == 3 - assert "a" in l - assert "b" in l - assert "c" in l + values = reprec.getcalls("pytest_runtest_call")[0].item.module.values + assert len(values) == 3 + assert "a" in values + assert "b" in values + assert "c" in values def test_scope_mismatch(self, testdir): testdir.makeconftest(""" @@ -2111,16 +2111,16 @@ class TestFixtureMarker(object): def arg(request): return request.param - l = [] + values = [] def test_1(arg): - l.append(arg) + values.append(arg) def test_2(arg): - l.append(arg) + values.append(arg) """) reprec = testdir.inline_run("-v") reprec.assertoutcome(passed=4) - l = reprec.getcalls("pytest_runtest_call")[0].item.module.l - assert l == [1, 1, 2, 2] + values = reprec.getcalls("pytest_runtest_call")[0].item.module.values + assert values == [1, 1, 2, 2] def test_module_parametrized_ordering(self, testdir): testdir.makeconftest(""" @@ -2172,7 +2172,7 @@ class TestFixtureMarker(object): testdir.makeconftest(""" import pytest - l = [] + values = [] @pytest.fixture(scope="function", params=[1,2]) def farg(request): @@ -2185,7 +2185,7 @@ class TestFixtureMarker(object): @pytest.fixture(scope="function", autouse=True) def append(request, farg, carg): def fin(): - l.append("fin_%s%s" % (carg, farg)) + values.append("fin_%s%s" % (carg, farg)) request.addfinalizer(fin) """) testdir.makepyfile(""" @@ -2223,30 +2223,30 @@ class TestFixtureMarker(object): @pytest.fixture(scope="function", params=[1, 2]) def arg(request): param = request.param - request.addfinalizer(lambda: l.append("fin:%s" % param)) - l.append("create:%s" % param) + request.addfinalizer(lambda: values.append("fin:%s" % param)) + values.append("create:%s" % param) return request.param @pytest.fixture(scope="module", params=["mod1", "mod2"]) def modarg(request): param = request.param - request.addfinalizer(lambda: l.append("fin:%s" % param)) - l.append("create:%s" % param) + request.addfinalizer(lambda: values.append("fin:%s" % param)) + values.append("create:%s" % param) return request.param - l = [] + values = [] def test_1(arg): - l.append("test1") + values.append("test1") def test_2(modarg): - l.append("test2") + values.append("test2") def test_3(arg, modarg): - l.append("test3") + values.append("test3") def test_4(modarg, arg): - l.append("test4") + values.append("test4") """) reprec = testdir.inline_run("-v") reprec.assertoutcome(passed=12) - l = reprec.getcalls("pytest_runtest_call")[0].item.module.l + values = reprec.getcalls("pytest_runtest_call")[0].item.module.values expected = [ 'create:1', 'test1', 'fin:1', 'create:2', 'test1', 'fin:2', 'create:mod1', 'test2', 'create:1', 'test3', @@ -2257,8 +2257,8 @@ class TestFixtureMarker(object): 'test4', 'fin:1', 'create:2', 'test4', 'fin:2', 'fin:mod2'] import pprint - pprint.pprint(list(zip(l, expected))) - assert l == expected + pprint.pprint(list(zip(values, expected))) + assert values == expected def test_parametrized_fixture_teardown_order(self, testdir): testdir.makepyfile(""" @@ -2267,29 +2267,29 @@ class TestFixtureMarker(object): def param1(request): return request.param - l = [] + values = [] class TestClass(object): @classmethod @pytest.fixture(scope="class", autouse=True) def setup1(self, request, param1): - l.append(1) + values.append(1) request.addfinalizer(self.teardown1) @classmethod def teardown1(self): - assert l.pop() == 1 + assert values.pop() == 1 @pytest.fixture(scope="class", autouse=True) def setup2(self, request, param1): - l.append(2) + values.append(2) request.addfinalizer(self.teardown2) @classmethod def teardown2(self): - assert l.pop() == 2 + assert values.pop() == 2 def test(self): pass def test_finish(): - assert not l + assert not values """) result = testdir.runpytest("-v") result.stdout.fnmatch_lines(""" @@ -2354,42 +2354,42 @@ class TestFixtureMarker(object): def test_request_is_clean(self, testdir): testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture(params=[1, 2]) def fix(request): - request.addfinalizer(lambda: l.append(request.param)) + request.addfinalizer(lambda: values.append(request.param)) def test_fix(fix): pass """) reprec = testdir.inline_run("-s") - l = reprec.getcalls("pytest_runtest_call")[0].item.module.l - assert l == [1, 2] + values = reprec.getcalls("pytest_runtest_call")[0].item.module.values + assert values == [1, 2] def test_parametrize_separated_lifecycle(self, testdir): testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture(scope="module", params=[1, 2]) def arg(request): x = request.param - request.addfinalizer(lambda: l.append("fin%s" % x)) + request.addfinalizer(lambda: values.append("fin%s" % x)) return request.param def test_1(arg): - l.append(arg) + values.append(arg) def test_2(arg): - l.append(arg) + values.append(arg) """) reprec = testdir.inline_run("-vs") reprec.assertoutcome(passed=4) - l = reprec.getcalls("pytest_runtest_call")[0].item.module.l + values = reprec.getcalls("pytest_runtest_call")[0].item.module.values import pprint - pprint.pprint(l) - # assert len(l) == 6 - assert l[0] == l[1] == 1 - assert l[2] == "fin1" - assert l[3] == l[4] == 2 - assert l[5] == "fin2" + pprint.pprint(values) + # assert len(values) == 6 + assert values[0] == values[1] == 1 + assert values[2] == "fin1" + assert values[3] == values[4] == 2 + assert values[5] == "fin2" def test_parametrize_function_scoped_finalizers_called(self, testdir): testdir.makepyfile(""" @@ -2398,17 +2398,17 @@ class TestFixtureMarker(object): @pytest.fixture(scope="function", params=[1, 2]) def arg(request): x = request.param - request.addfinalizer(lambda: l.append("fin%s" % x)) + request.addfinalizer(lambda: values.append("fin%s" % x)) return request.param - l = [] + values = [] def test_1(arg): - l.append(arg) + values.append(arg) def test_2(arg): - l.append(arg) + values.append(arg) def test_3(): - assert len(l) == 8 - assert l == [1, "fin1", 2, "fin2", 1, "fin1", 2, "fin2"] + assert len(values) == 8 + assert values == [1, "fin1", 2, "fin2", 1, "fin1", 2, "fin2"] """) reprec = testdir.inline_run("-v") reprec.assertoutcome(passed=5) @@ -2418,7 +2418,7 @@ class TestFixtureMarker(object): def test_finalizer_order_on_parametrization(self, scope, testdir): testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture(scope=%(scope)r, params=["1"]) def fix1(request): @@ -2427,13 +2427,13 @@ class TestFixtureMarker(object): @pytest.fixture(scope=%(scope)r) def fix2(request, base): def cleanup_fix2(): - assert not l, "base should not have been finalized" + assert not values, "base should not have been finalized" request.addfinalizer(cleanup_fix2) @pytest.fixture(scope=%(scope)r) def base(request, fix1): def cleanup_base(): - l.append("fin_base") + values.append("fin_base") print ("finalizing base") request.addfinalizer(cleanup_base) @@ -2451,29 +2451,29 @@ class TestFixtureMarker(object): def test_class_scope_parametrization_ordering(self, testdir): testdir.makepyfile(""" import pytest - l = [] + values = [] @pytest.fixture(params=["John", "Doe"], scope="class") def human(request): - request.addfinalizer(lambda: l.append("fin %s" % request.param)) + request.addfinalizer(lambda: values.append("fin %s" % request.param)) return request.param class TestGreetings(object): def test_hello(self, human): - l.append("test_hello") + values.append("test_hello") class TestMetrics(object): def test_name(self, human): - l.append("test_name") + values.append("test_name") def test_population(self, human): - l.append("test_population") + values.append("test_population") """) reprec = testdir.inline_run() reprec.assertoutcome(passed=6) - l = reprec.getcalls("pytest_runtest_call")[0].item.module.l - assert l == ["test_hello", "fin John", "test_hello", "fin Doe", - "test_name", "test_population", "fin John", - "test_name", "test_population", "fin Doe"] + values = reprec.getcalls("pytest_runtest_call")[0].item.module.values + assert values == ["test_hello", "fin John", "test_hello", "fin Doe", + "test_name", "test_population", "fin John", + "test_name", "test_population", "fin Doe"] def test_parametrize_setup_function(self, testdir): testdir.makepyfile(""" @@ -2485,21 +2485,21 @@ class TestFixtureMarker(object): @pytest.fixture(scope="module", autouse=True) def mysetup(request, arg): - request.addfinalizer(lambda: l.append("fin%s" % arg)) - l.append("setup%s" % arg) + request.addfinalizer(lambda: values.append("fin%s" % arg)) + values.append("setup%s" % arg) - l = [] + values = [] def test_1(arg): - l.append(arg) + values.append(arg) def test_2(arg): - l.append(arg) + values.append(arg) def test_3(): import pprint - pprint.pprint(l) + pprint.pprint(values) if arg == 1: - assert l == ["setup1", 1, 1, ] + assert values == ["setup1", 1, 1, ] elif arg == 2: - assert l == ["setup1", 1, 1, "fin1", + assert values == ["setup1", 1, 1, "fin1", "setup2", 2, 2, ] """) @@ -2660,13 +2660,13 @@ class TestErrors(object): request.addfinalizer(f) return object() - l = [] + values = [] def test_1(fix1): - l.append(fix1) + values.append(fix1) def test_2(fix1): - l.append(fix1) + values.append(fix1) def test_3(): - assert l[0] != l[1] + assert values[0] != values[1] """) result = testdir.runpytest() result.stdout.fnmatch_lines(""" diff --git a/testing/python/integration.py b/testing/python/integration.py index 1a98eae2a..6ea29fa98 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -93,8 +93,8 @@ class TestMockDecoration(object): def f(x): pass - l = getfuncargnames(f) - assert l == ("x",) + values = getfuncargnames(f) + assert values == ("x",) def test_wrapped_getfuncargnames_patching(self): from _pytest.compat import getfuncargnames @@ -110,8 +110,8 @@ class TestMockDecoration(object): def f(x, y, z): pass - l = getfuncargnames(f) - assert l == ("y", "z") + values = getfuncargnames(f) + assert values == ("y", "z") def test_unittest_mock(self, testdir): pytest.importorskip("unittest.mock") diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index a0025a15a..2acdf669e 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -1071,21 +1071,21 @@ class TestMetafuncFunctional(object): def test_parametrize_scope_overrides(self, testdir, scope, length): testdir.makepyfile(""" import pytest - l = [] + values = [] def pytest_generate_tests(metafunc): if "arg" in metafunc.funcargnames: metafunc.parametrize("arg", [1,2], indirect=True, scope=%r) @pytest.fixture def arg(request): - l.append(request.param) + values.append(request.param) return request.param def test_hello(arg): assert arg in (1,2) def test_world(arg): assert arg in (1,2) def test_checklength(): - assert len(l) == %d + assert len(values) == %d """ % (scope, length)) reprec = testdir.inline_run() reprec.assertoutcome(passed=5) diff --git a/testing/python/show_fixtures_per_test.py b/testing/python/show_fixtures_per_test.py index 18563e818..741f33946 100644 --- a/testing/python/show_fixtures_per_test.py +++ b/testing/python/show_fixtures_per_test.py @@ -135,3 +135,24 @@ def test_verbose_include_private_fixtures_and_loc(testdir): 'arg3 -- test_verbose_include_private_fixtures_and_loc.py:3', ' arg3 from testmodule', ]) + + +def test_doctest_items(testdir): + testdir.makepyfile(''' + def foo(): + """ + >>> 1 + 1 + 2 + """ + ''') + testdir.maketxtfile(''' + >>> 1 + 1 + 2 + ''') + result = testdir.runpytest("--fixtures-per-test", "--doctest-modules", + "--doctest-glob=*.txt", "-v") + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + '*collected 2 items*', + ]) diff --git a/testing/test_argcomplete.py b/testing/test_argcomplete.py index b07f6e83b..c92612577 100644 --- a/testing/test_argcomplete.py +++ b/testing/test_argcomplete.py @@ -82,7 +82,7 @@ class TestArgComplete(object): from _pytest._argcomplete import FastFilesCompleter ffc = FastFilesCompleter() fc = FilesCompleter() - for x in '/ /d /data qqq'.split(): + for x in ['/', '/d', '/data', 'qqq', '']: assert equal_with_bash(x, ffc, fc, out=py.std.sys.stdout) @pytest.mark.skipif("sys.platform in ('win32', 'darwin')") diff --git a/testing/test_assertion.py b/testing/test_assertion.py index a5174cc8c..4cd050d8c 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -229,9 +229,9 @@ class TestImportHookInstallation(object): return pkg.helper.tool """, 'pkg/other.py': """ - l = [3, 2] + values = [3, 2] def tool(): - assert l.pop() == 3 + assert values.pop() == 3 """, 'conftest.py': """ pytest_plugins = ['pkg.plugin'] @@ -248,7 +248,7 @@ class TestImportHookInstallation(object): result = testdir.runpytest_subprocess('--assert=rewrite') result.stdout.fnmatch_lines(['>*assert a == b*', 'E*assert 2 == 3*', - '>*assert l.pop() == 3*', + '>*assert values.pop() == 3*', 'E*AssertionError']) def test_register_assert_rewrite_checks_types(self): @@ -263,13 +263,13 @@ class TestBinReprIntegration(object): def test_pytest_assertrepr_compare_called(self, testdir): testdir.makeconftest(""" import pytest - l = [] + values = [] def pytest_assertrepr_compare(op, left, right): - l.append((op, left, right)) + values.append((op, left, right)) @pytest.fixture def list(request): - return l + return values """) testdir.makepyfile(""" def test_hello(): diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 2d61b7440..31e996052 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -65,13 +65,18 @@ class TestAssertionRewrite(object): def test_place_initial_imports(self): s = """'Doc string'\nother = stuff""" m = rewrite(s) - assert isinstance(m.body[0], ast.Expr) - assert isinstance(m.body[0].value, ast.Str) - for imp in m.body[1:3]: + # Module docstrings in 3.7 are part of Module node, it's not in the body + # so we remove it so the following body items have the same indexes on + # all Python versions + if sys.version_info < (3, 7): + assert isinstance(m.body[0], ast.Expr) + assert isinstance(m.body[0].value, ast.Str) + del m.body[0] + for imp in m.body[0:2]: assert isinstance(imp, ast.Import) assert imp.lineno == 2 assert imp.col_offset == 0 - assert isinstance(m.body[3], ast.Assign) + assert isinstance(m.body[2], ast.Assign) s = """from __future__ import with_statement\nother_stuff""" m = rewrite(s) assert isinstance(m.body[0], ast.ImportFrom) @@ -80,16 +85,29 @@ class TestAssertionRewrite(object): assert imp.lineno == 2 assert imp.col_offset == 0 assert isinstance(m.body[3], ast.Expr) + s = """'doc string'\nfrom __future__ import with_statement""" + m = rewrite(s) + if sys.version_info < (3, 7): + assert isinstance(m.body[0], ast.Expr) + assert isinstance(m.body[0].value, ast.Str) + del m.body[0] + assert isinstance(m.body[0], ast.ImportFrom) + for imp in m.body[1:3]: + assert isinstance(imp, ast.Import) + assert imp.lineno == 2 + assert imp.col_offset == 0 s = """'doc string'\nfrom __future__ import with_statement\nother""" m = rewrite(s) - assert isinstance(m.body[0], ast.Expr) - assert isinstance(m.body[0].value, ast.Str) - assert isinstance(m.body[1], ast.ImportFrom) - for imp in m.body[2:4]: + if sys.version_info < (3, 7): + assert isinstance(m.body[0], ast.Expr) + assert isinstance(m.body[0].value, ast.Str) + del m.body[0] + assert isinstance(m.body[0], ast.ImportFrom) + for imp in m.body[1:3]: assert isinstance(imp, ast.Import) assert imp.lineno == 3 assert imp.col_offset == 0 - assert isinstance(m.body[4], ast.Expr) + assert isinstance(m.body[3], ast.Expr) s = """from . import relative\nother_stuff""" m = rewrite(s) for imp in m.body[0:2]: @@ -101,10 +119,14 @@ class TestAssertionRewrite(object): def test_dont_rewrite(self): s = """'PYTEST_DONT_REWRITE'\nassert 14""" m = rewrite(s) - assert len(m.body) == 2 - assert isinstance(m.body[0].value, ast.Str) - assert isinstance(m.body[1], ast.Assert) - assert m.body[1].msg is None + if sys.version_info < (3, 7): + assert len(m.body) == 2 + assert isinstance(m.body[0], ast.Expr) + assert isinstance(m.body[0].value, ast.Str) + del m.body[0] + else: + assert len(m.body) == 1 + assert m.body[0].msg is None def test_name(self): def f(): @@ -451,8 +473,8 @@ class TestAssertionRewrite(object): def test_len(self): def f(): - l = list(range(10)) - assert len(l) == 11 + values = list(range(10)) + assert len(values) == 11 assert getmsg(f).startswith("""assert 10 == 11 + where 10 = len([""") diff --git a/testing/test_collection.py b/testing/test_collection.py index 5d1654410..cf13d4b00 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, division, print_function import pytest import py +import _pytest._code from _pytest.main import Session, EXIT_NOTESTSCOLLECTED, _in_venv @@ -702,9 +703,9 @@ class TestNodekeywords(object): def test_pass(): pass def test_fail(): assert 0 """) - l = list(modcol.keywords) - assert modcol.name in l - for x in l: + values = list(modcol.keywords) + assert modcol.name in values + for x in values: assert not x.startswith("_") assert modcol.name in repr(modcol.keywords) @@ -830,3 +831,28 @@ def test_continue_on_collection_errors_maxfail(testdir): "*Interrupted: stopping after 3 failures*", "*1 failed, 2 error*", ]) + + +def test_fixture_scope_sibling_conftests(testdir): + """Regression test case for https://github.com/pytest-dev/pytest/issues/2836""" + foo_path = testdir.mkpydir("foo") + foo_path.join("conftest.py").write(_pytest._code.Source(""" + import pytest + @pytest.fixture + def fix(): + return 1 + """)) + foo_path.join("test_foo.py").write("def test_foo(fix): assert fix == 1") + + # Tests in `food/` should not see the conftest fixture from `foo/` + food_path = testdir.mkpydir("food") + food_path.join("test_food.py").write("def test_food(fix): assert fix == 1") + + res = testdir.runpytest() + assert res.ret == 1 + + res.stdout.fnmatch_lines([ + "*ERROR at setup of test_food*", + "E*fixture 'fix' not found", + "*1 passed, 1 error*", + ]) diff --git a/testing/test_config.py b/testing/test_config.py index 3cad6d587..f21d1821e 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -123,11 +123,11 @@ class TestConfigCmdlineParsing(object): class TestConfigAPI(object): def test_config_trace(self, testdir): config = testdir.parseconfig() - l = [] - config.trace.root.setwriter(l.append) + values = [] + config.trace.root.setwriter(values.append) config.trace("hello") - assert len(l) == 1 - assert l[0] == "hello [config]\n" + assert len(values) == 1 + assert values[0] == "hello [config]\n" def test_config_getoption(self, testdir): testdir.makeconftest(""" @@ -209,10 +209,10 @@ class TestConfigAPI(object): paths=hello world/sub.py """) config = testdir.parseconfig() - l = config.getini("paths") - assert len(l) == 2 - assert l[0] == p.dirpath('hello') - assert l[1] == p.dirpath('world/sub.py') + values = config.getini("paths") + assert len(values) == 2 + assert values[0] == p.dirpath('hello') + assert values[1] == p.dirpath('world/sub.py') pytest.raises(ValueError, config.getini, 'other') def test_addini_args(self, testdir): @@ -226,11 +226,11 @@ class TestConfigAPI(object): args=123 "123 hello" "this" """) config = testdir.parseconfig() - l = config.getini("args") - assert len(l) == 3 - assert l == ["123", "123 hello", "this"] - l = config.getini("a2") - assert l == list("123") + values = config.getini("args") + assert len(values) == 3 + assert values == ["123", "123 hello", "this"] + values = config.getini("a2") + assert values == list("123") def test_addini_linelist(self, testdir): testdir.makeconftest(""" @@ -244,11 +244,11 @@ class TestConfigAPI(object): second line """) config = testdir.parseconfig() - l = config.getini("xy") - assert len(l) == 2 - assert l == ["123 345", "second line"] - l = config.getini("a2") - assert l == [] + values = config.getini("xy") + assert len(values) == 2 + assert values == ["123 345", "second line"] + values = config.getini("a2") + assert values == [] @pytest.mark.parametrize('str_val, bool_val', [('True', True), ('no', False), ('no-ini', True)]) @@ -275,13 +275,13 @@ class TestConfigAPI(object): xy= 123 """) config = testdir.parseconfig() - l = config.getini("xy") - assert len(l) == 1 - assert l == ["123"] + values = config.getini("xy") + assert len(values) == 1 + assert values == ["123"] config.addinivalue_line("xy", "456") - l = config.getini("xy") - assert len(l) == 2 - assert l == ["123", "456"] + values = config.getini("xy") + assert len(values) == 2 + assert values == ["123", "456"] def test_addinivalue_line_new(self, testdir): testdir.makeconftest(""" @@ -291,13 +291,13 @@ class TestConfigAPI(object): config = testdir.parseconfig() assert not config.getini("xy") config.addinivalue_line("xy", "456") - l = config.getini("xy") - assert len(l) == 1 - assert l == ["456"] + values = config.getini("xy") + assert len(values) == 1 + assert values == ["456"] config.addinivalue_line("xy", "123") - l = config.getini("xy") - assert len(l) == 2 - assert l == ["456", "123"] + values = config.getini("xy") + assert len(values) == 2 + assert values == ["456", "123"] def test_confcutdir_check_isdir(self, testdir): """Give an error if --confcutdir is not a valid directory (#2078)""" @@ -596,13 +596,13 @@ def test_load_initial_conftest_last_ordering(testdir): m = My() pm.register(m) hc = pm.hook.pytest_load_initial_conftests - l = hc._nonwrappers + hc._wrappers + values = hc._nonwrappers + hc._wrappers expected = [ "_pytest.config", 'test_config', '_pytest.capture', ] - assert [x.function.__module__ for x in l] == expected + assert [x.function.__module__ for x in values] == expected def test_get_plugin_specs_as_list(): @@ -623,17 +623,17 @@ def test_get_plugin_specs_as_list(): class TestWarning(object): def test_warn_config(self, testdir): testdir.makeconftest(""" - l = [] + values = [] def pytest_configure(config): config.warn("C1", "hello") def pytest_logwarning(code, message): if message == "hello" and code == "C1": - l.append(1) + values.append(1) """) testdir.makepyfile(""" def test_proper(pytestconfig): import conftest - assert conftest.l == [1] + assert conftest.values == [1] """) reprec = testdir.inline_run() reprec.assertoutcome(passed=1) diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 39590f5f2..c0411b723 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -87,8 +87,8 @@ def test_doubledash_considered(testdir): conf.join("conftest.py").ensure() conftest = PytestPluginManager() conftest_setinitial(conftest, [conf.basename, conf.basename]) - l = conftest._getconftestmodules(conf) - assert len(l) == 1 + values = conftest._getconftestmodules(conf) + assert len(values) == 1 def test_issue151_load_all_conftests(testdir): @@ -130,28 +130,28 @@ def test_conftestcutdir(testdir): p = testdir.mkdir("x") conftest = PytestPluginManager() conftest_setinitial(conftest, [testdir.tmpdir], confcutdir=p) - l = conftest._getconftestmodules(p) - assert len(l) == 0 - l = conftest._getconftestmodules(conf.dirpath()) - assert len(l) == 0 + values = conftest._getconftestmodules(p) + assert len(values) == 0 + values = conftest._getconftestmodules(conf.dirpath()) + assert len(values) == 0 assert conf not in conftest._conftestpath2mod # but we can still import a conftest directly conftest._importconftest(conf) - l = conftest._getconftestmodules(conf.dirpath()) - assert l[0].__file__.startswith(str(conf)) + values = conftest._getconftestmodules(conf.dirpath()) + assert values[0].__file__.startswith(str(conf)) # and all sub paths get updated properly - l = conftest._getconftestmodules(p) - assert len(l) == 1 - assert l[0].__file__.startswith(str(conf)) + values = conftest._getconftestmodules(p) + assert len(values) == 1 + assert values[0].__file__.startswith(str(conf)) def test_conftestcutdir_inplace_considered(testdir): conf = testdir.makeconftest("") conftest = PytestPluginManager() conftest_setinitial(conftest, [conf.dirpath()], confcutdir=conf.dirpath()) - l = conftest._getconftestmodules(conf.dirpath()) - assert len(l) == 1 - assert l[0].__file__.startswith(str(conf)) + values = conftest._getconftestmodules(conf.dirpath()) + assert len(values) == 1 + assert values[0].__file__.startswith(str(conf)) @pytest.mark.parametrize("name", 'test tests whatever .dotdir'.split()) diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 8a81ea0ed..6616d2eae 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -173,7 +173,7 @@ class TestDoctests(object): "*UNEXPECTED*ZeroDivision*", ]) - def test_docstring_context_around_error(self, testdir): + def test_docstring_partial_context_around_error(self, testdir): """Test that we show some context before the actual line of a failing doctest. """ @@ -199,7 +199,7 @@ class TestDoctests(object): ''') result = testdir.runpytest('--doctest-modules') result.stdout.fnmatch_lines([ - '*docstring_context_around_error*', + '*docstring_partial_context_around_error*', '005*text-line-3', '006*text-line-4', '013*text-line-11', @@ -213,6 +213,32 @@ class TestDoctests(object): assert 'text-line-2' not in result.stdout.str() assert 'text-line-after' not in result.stdout.str() + def test_docstring_full_context_around_error(self, testdir): + """Test that we show the whole context before the actual line of a failing + doctest, provided that the context is up to 10 lines long. + """ + testdir.makepyfile(''' + def foo(): + """ + text-line-1 + text-line-2 + + >>> 1 + 1 + 3 + """ + ''') + result = testdir.runpytest('--doctest-modules') + result.stdout.fnmatch_lines([ + '*docstring_full_context_around_error*', + '003*text-line-1', + '004*text-line-2', + '006*>>> 1 + 1', + 'Expected:', + ' 3', + 'Got:', + ' 2', + ]) + def test_doctest_linedata_missing(self, testdir): testdir.tmpdir.join('hello.py').write(_pytest._code.Source(""" class Fun(object): diff --git a/testing/test_mark.py b/testing/test_mark.py index ae070f3a0..3ac42daee 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -169,6 +169,23 @@ def test_markers_option(testdir): ]) +def test_ini_markers_whitespace(testdir): + testdir.makeini(""" + [pytest] + markers = + a1 : this is a whitespace marker + """) + testdir.makepyfile(""" + import pytest + + @pytest.mark.a1 + def test_markers(): + assert True + """) + rec = testdir.inline_run("--strict", "-m", "a1") + rec.assertoutcome(passed=1) + + def test_markers_option_with_plugin_in_current_dir(testdir): testdir.makeconftest('pytest_plugins = "flip_flop"') testdir.makepyfile(flip_flop="""\ @@ -342,6 +359,24 @@ def test_parametrized_collect_with_wrong_args(testdir): ]) +def test_parametrized_with_kwargs(testdir): + """Test collect parametrized func with wrong number of args.""" + py_file = testdir.makepyfile(""" + import pytest + + @pytest.fixture(params=[1,2]) + def a(request): + return request.param + + @pytest.mark.parametrize(argnames='b', argvalues=[1, 2]) + def test_func(a, b): + pass + """) + + result = testdir.runpytest(py_file) + assert(result.ret == 0) + + class TestFunctional(object): def test_mark_per_function(self, testdir): @@ -433,11 +468,11 @@ class TestFunctional(object): assert marker.kwargs == {'x': 1, 'y': 2, 'z': 4} # test the new __iter__ interface - l = list(marker) - assert len(l) == 3 - assert l[0].args == ("pos0",) - assert l[1].args == () - assert l[2].args == ("pos1", ) + values = list(marker) + assert len(values) == 3 + assert values[0].args == ("pos0",) + assert values[1].args == () + assert values[2].args == ("pos1", ) @pytest.mark.xfail(reason='unfixed') def test_merging_markers_deep(self, testdir): @@ -529,9 +564,9 @@ class TestFunctional(object): def test_func(): pass """) - l = reprec.getfailedcollections() - assert len(l) == 1 - assert "TypeError" in str(l[0].longrepr) + values = reprec.getfailedcollections() + assert len(values) == 1 + assert "TypeError" in str(values[0].longrepr) def test_mark_dynamically_in_funcarg(self, testdir): testdir.makeconftest(""" @@ -540,8 +575,8 @@ class TestFunctional(object): def arg(request): request.applymarker(pytest.mark.hello) def pytest_terminal_summary(terminalreporter): - l = terminalreporter.stats['passed'] - terminalreporter.writer.line("keyword: %s" % l[0].keywords) + values = terminalreporter.stats['passed'] + terminalreporter.writer.line("keyword: %s" % values[0].keywords) """) testdir.makepyfile(""" def test_func(arg): @@ -564,10 +599,10 @@ class TestFunctional(object): item, = items keywords = item.keywords marker = keywords['hello'] - l = list(marker) - assert len(l) == 2 - assert l[0].args == ("pos0",) - assert l[1].args == ("pos1",) + values = list(marker) + assert len(values) == 2 + assert values[0].args == ("pos0",) + assert values[1].args == ("pos1",) def test_no_marker_match_on_unmarked_names(self, testdir): p = testdir.makepyfile(""" diff --git a/testing/test_nodes.py b/testing/test_nodes.py new file mode 100644 index 000000000..6f4540f99 --- /dev/null +++ b/testing/test_nodes.py @@ -0,0 +1,18 @@ +import pytest + +from _pytest import nodes + + +@pytest.mark.parametrize("baseid, nodeid, expected", ( + ('', '', True), + ('', 'foo', True), + ('', 'foo/bar', True), + ('', 'foo/bar::TestBaz::()', True), + ('foo', 'food', False), + ('foo/bar::TestBaz::()', 'foo/bar', False), + ('foo/bar::TestBaz::()', 'foo/bar::TestBop::()', False), + ('foo/bar', 'foo/bar::TestBop::()', True), +)) +def test_ischildnode(baseid, nodeid, expected): + result = nodes.ischildnode(baseid, nodeid) + assert result is expected diff --git a/testing/test_nose.py b/testing/test_nose.py index 872922ed4..df3e1a94b 100644 --- a/testing/test_nose.py +++ b/testing/test_nose.py @@ -8,18 +8,18 @@ def setup_module(mod): def test_nose_setup(testdir): p = testdir.makepyfile(""" - l = [] + values = [] from nose.tools import with_setup - @with_setup(lambda: l.append(1), lambda: l.append(2)) + @with_setup(lambda: values.append(1), lambda: values.append(2)) def test_hello(): - assert l == [1] + assert values == [1] def test_world(): - assert l == [1,2] + assert values == [1,2] - test_hello.setup = lambda: l.append(1) - test_hello.teardown = lambda: l.append(2) + test_hello.setup = lambda: values.append(1) + test_hello.teardown = lambda: values.append(2) """) result = testdir.runpytest(p, '-p', 'nose') result.assert_outcomes(passed=2) @@ -27,15 +27,15 @@ def test_nose_setup(testdir): def test_setup_func_with_setup_decorator(): from _pytest.nose import call_optional - l = [] + values = [] class A(object): @pytest.fixture(autouse=True) def f(self): - l.append(1) + values.append(1) call_optional(A(), "f") - assert not l + assert not values def test_setup_func_not_callable(): @@ -51,24 +51,24 @@ def test_nose_setup_func(testdir): p = testdir.makepyfile(""" from nose.tools import with_setup - l = [] + values = [] def my_setup(): a = 1 - l.append(a) + values.append(a) def my_teardown(): b = 2 - l.append(b) + values.append(b) @with_setup(my_setup, my_teardown) def test_hello(): - print (l) - assert l == [1] + print (values) + assert values == [1] def test_world(): - print (l) - assert l == [1,2] + print (values) + assert values == [1,2] """) result = testdir.runpytest(p, '-p', 'nose') @@ -79,18 +79,18 @@ def test_nose_setup_func_failure(testdir): p = testdir.makepyfile(""" from nose.tools import with_setup - l = [] + values = [] my_setup = lambda x: 1 my_teardown = lambda x: 2 @with_setup(my_setup, my_teardown) def test_hello(): - print (l) - assert l == [1] + print (values) + assert values == [1] def test_world(): - print (l) - assert l == [1,2] + print (values) + assert values == [1,2] """) result = testdir.runpytest(p, '-p', 'nose') @@ -101,13 +101,13 @@ def test_nose_setup_func_failure(testdir): def test_nose_setup_func_failure_2(testdir): testdir.makepyfile(""" - l = [] + values = [] my_setup = 1 my_teardown = 2 def test_hello(): - assert l == [] + assert values == [] test_hello.setup = my_setup test_hello.teardown = my_teardown @@ -121,26 +121,26 @@ def test_nose_setup_partial(testdir): p = testdir.makepyfile(""" from functools import partial - l = [] + values = [] def my_setup(x): a = x - l.append(a) + values.append(a) def my_teardown(x): b = x - l.append(b) + values.append(b) my_setup_partial = partial(my_setup, 1) my_teardown_partial = partial(my_teardown, 2) def test_hello(): - print (l) - assert l == [1] + print (values) + assert values == [1] def test_world(): - print (l) - assert l == [1,2] + print (values) + assert values == [1,2] test_hello.setup = my_setup_partial test_hello.teardown = my_teardown_partial @@ -251,19 +251,19 @@ def test_module_level_setup(testdir): def test_nose_style_setup_teardown(testdir): testdir.makepyfile(""" - l = [] + values = [] def setup_module(): - l.append(1) + values.append(1) def teardown_module(): - del l[0] + del values[0] def test_hello(): - assert l == [1] + assert values == [1] def test_world(): - assert l == [1] + assert values == [1] """) result = testdir.runpytest('-p', 'nose') result.stdout.fnmatch_lines([ diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index be7980c26..13c487c26 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -85,23 +85,23 @@ class TestPytestPluginInteractions(object): def test_configure(self, testdir): config = testdir.parseconfig() - l = [] + values = [] class A(object): def pytest_configure(self, config): - l.append(self) + values.append(self) config.pluginmanager.register(A()) - assert len(l) == 0 + assert len(values) == 0 config._do_configure() - assert len(l) == 1 + assert len(values) == 1 config.pluginmanager.register(A()) # leads to a configured() plugin - assert len(l) == 2 - assert l[0] != l[1] + assert len(values) == 2 + assert values[0] != values[1] config._ensure_unconfigure() config.pluginmanager.register(A()) - assert len(l) == 2 + assert len(values) == 2 def test_hook_tracing(self): pytestpm = get_config().pluginmanager # fully initialized with plugins @@ -116,19 +116,19 @@ class TestPytestPluginInteractions(object): saveindent.append(pytestpm.trace.root.indent) raise ValueError() - l = [] - pytestpm.trace.root.setwriter(l.append) + values = [] + pytestpm.trace.root.setwriter(values.append) undo = pytestpm.enable_tracing() try: indent = pytestpm.trace.root.indent p = api1() pytestpm.register(p) assert pytestpm.trace.root.indent == indent - assert len(l) >= 2 - assert 'pytest_plugin_registered' in l[0] - assert 'finish' in l[1] + assert len(values) >= 2 + assert 'pytest_plugin_registered' in values[0] + assert 'finish' in values[1] - l[:] = [] + values[:] = [] with pytest.raises(ValueError): pytestpm.register(api2()) assert pytestpm.trace.root.indent == indent @@ -230,12 +230,12 @@ class TestPytestPluginManager(object): mod = py.std.types.ModuleType("x.y.pytest_hello") pm.register(mod) assert pm.is_registered(mod) - l = pm.get_plugins() - assert mod in l + values = pm.get_plugins() + assert mod in values pytest.raises(ValueError, "pm.register(mod)") pytest.raises(ValueError, lambda: pm.register(mod)) # assert not pm.is_registered(mod2) - assert pm.get_plugins() == l + assert pm.get_plugins() == values def test_canonical_import(self, monkeypatch): mod = py.std.types.ModuleType("pytest_xyz") @@ -269,8 +269,8 @@ class TestPytestPluginManager(object): # check that it is not registered twice pytestpm.consider_module(mod) - l = reprec.getcalls("pytest_plugin_registered") - assert len(l) == 1 + values = reprec.getcalls("pytest_plugin_registered") + assert len(values) == 1 def test_consider_env_fails_to_import(self, monkeypatch, pytestpm): monkeypatch.setenv('PYTEST_PLUGINS', 'nonexisting', prepend=",") diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 6895b1140..481bf0a04 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -30,10 +30,10 @@ class TestWarningsRecorderChecker(object): assert len(rec.list) == 2 warn = rec.pop() assert str(warn.message) == "hello" - l = rec.list + values = rec.list rec.clear() assert len(rec.list) == 0 - assert l is rec.list + assert values is rec.list pytest.raises(AssertionError, "rec.pop()") def test_typechecking(self): diff --git a/testing/test_runner.py b/testing/test_runner.py index 1ab449ba3..84d8f6c71 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -13,12 +13,12 @@ class TestSetupState(object): def test_setup(self, testdir): ss = runner.SetupState() item = testdir.getitem("def test_func(): pass") - l = [1] + values = [1] ss.prepare(item) - ss.addfinalizer(l.pop, colitem=item) - assert l + ss.addfinalizer(values.pop, colitem=item) + assert values ss._pop_and_teardown() - assert not l + assert not values def test_teardown_exact_stack_empty(self, testdir): item = testdir.getitem("def test_func(): pass") diff --git a/testing/test_runner_xunit.py b/testing/test_runner_xunit.py index f01eaca13..fc931f867 100644 --- a/testing/test_runner_xunit.py +++ b/testing/test_runner_xunit.py @@ -39,20 +39,20 @@ def test_module_and_function_setup(testdir): def test_module_setup_failure_no_teardown(testdir): reprec = testdir.inline_runsource(""" - l = [] + values = [] def setup_module(module): - l.append(1) + values.append(1) 0/0 def test_nothing(): pass def teardown_module(module): - l.append(2) + values.append(2) """) reprec.assertoutcome(failed=1) calls = reprec.getcalls("pytest_runtest_setup") - assert calls[0].item.module.l == [1] + assert calls[0].item.module.values == [1] def test_setup_function_failure_no_teardown(testdir): diff --git a/testing/test_session.py b/testing/test_session.py index f9eb95f3b..9ec13f523 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -45,9 +45,9 @@ class SessionTests(object): a = 1 """) reprec = testdir.inline_run(tfile) - l = reprec.getfailedcollections() - assert len(l) == 1 - out = str(l[0].longrepr) + values = reprec.getfailedcollections() + assert len(values) == 1 + out = str(values[0].longrepr) assert out.find('does_not_work') != -1 def test_raises_output(self, testdir): @@ -75,9 +75,9 @@ class SessionTests(object): def test_syntax_error_module(self, testdir): reprec = testdir.inline_runsource("this is really not python") - l = reprec.getfailedcollections() - assert len(l) == 1 - out = str(l[0].longrepr) + values = reprec.getfailedcollections() + assert len(values) == 1 + out = str(values[0].longrepr) assert out.find(str('not python')) != -1 def test_exit_first_problem(self, testdir): @@ -144,15 +144,15 @@ class TestNewSession(SessionTests): def test_order_of_execution(self, testdir): reprec = testdir.inline_runsource(""" - l = [] + values = [] def test_1(): - l.append(1) + values.append(1) def test_2(): - l.append(2) + values.append(2) def test_3(): - assert l == [1,2] + assert values == [1,2] class Testmygroup(object): - reslist = l + reslist = values def test_1(self): self.reslist.append(1) def test_2(self): @@ -242,13 +242,13 @@ def test_exclude(testdir): def test_sessionfinish_with_start(testdir): testdir.makeconftest(""" import os - l = [] + values = [] def pytest_sessionstart(): - l.append(os.getcwd()) + values.append(os.getcwd()) os.chdir("..") def pytest_sessionfinish(): - assert l[0] == os.getcwd() + assert values[0] == os.getcwd() """) res = testdir.runpytest("--collect-only") diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 6608ccadf..a25c9460a 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -679,9 +679,9 @@ def test_skip_reasons_folding(): ev2.longrepr = longrepr ev2.skipped = True - l = folded_skips([ev1, ev2]) - assert len(l) == 1 - num, fspath, lineno, reason = l[0] + values = folded_skips([ev1, ev2]) + assert len(values) == 1 + num, fspath, lineno, reason = values[0] assert num == 2 assert fspath == path assert lineno == lineno diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 9bd607783..0fa98b26e 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -24,12 +24,12 @@ class Option(object): @property def args(self): - l = [] + values = [] if self.verbose: - l.append('-v') + values.append('-v') if self.fulltrace: - l.append('--fulltrace') - return l + values.append('--fulltrace') + return values def pytest_generate_tests(metafunc): diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 84f432a54..3273e81aa 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -73,19 +73,19 @@ def test_setup(testdir): def test_setUpModule(testdir): testpath = testdir.makepyfile(""" - l = [] + values = [] def setUpModule(): - l.append(1) + values.append(1) def tearDownModule(): - del l[0] + del values[0] def test_hello(): - assert l == [1] + assert values == [1] def test_world(): - assert l == [1] + assert values == [1] """) result = testdir.runpytest(testpath) result.stdout.fnmatch_lines([ @@ -95,13 +95,13 @@ def test_setUpModule(testdir): def test_setUpModule_failing_no_teardown(testdir): testpath = testdir.makepyfile(""" - l = [] + values = [] def setUpModule(): 0/0 def tearDownModule(): - l.append(1) + values.append(1) def test_hello(): pass @@ -109,7 +109,7 @@ def test_setUpModule_failing_no_teardown(testdir): reprec = testdir.inline_run(testpath) reprec.assertoutcome(passed=0, failed=1) call = reprec.getcalls("pytest_runtest_setup")[0] - assert not call.item.module.l + assert not call.item.module.values def test_new_instances(testdir): @@ -129,14 +129,14 @@ def test_teardown(testdir): testpath = testdir.makepyfile(""" import unittest class MyTestCase(unittest.TestCase): - l = [] + values = [] def test_one(self): pass def tearDown(self): - self.l.append(None) + self.values.append(None) class Second(unittest.TestCase): def test_check(self): - self.assertEqual(MyTestCase.l, [None]) + self.assertEqual(MyTestCase.values, [None]) """) reprec = testdir.inline_run(testpath) passed, skipped, failed = reprec.countoutcomes() diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 1328cc3f2..12539f8ee 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -144,6 +144,8 @@ def test_unicode(testdir, pyfile_with_warnings): @pytest.mark.skipif(sys.version_info >= (3, 0), reason='warnings message is broken as it is not str instance') def test_py2_unicode(testdir, pyfile_with_warnings): + if getattr(sys, "pypy_version_info", ())[:2] == (5, 9) and sys.platform.startswith('win'): + pytest.xfail("fails with unicode error on PyPy2 5.9 and Windows (#2905)") testdir.makepyfile(''' # -*- coding: utf8 -*- import warnings diff --git a/tox.ini b/tox.ini index a989fe56f..48913bd7b 100644 --- a/tox.ini +++ b/tox.ini @@ -12,7 +12,7 @@ envlist = py36 py37 pypy - {py27,py35}-{pexpect,xdist,trial,numpy} + {py27,py36}-{pexpect,xdist,trial,numpy} py27-nobyte doctesting py35-freeze @@ -37,7 +37,6 @@ deps = [testenv:py27-subprocess] changedir = . -basepython = python2.7 deps = pytest-xdist>=1.13 mock @@ -65,10 +64,11 @@ deps = mock nose hypothesis>=3.5.2 +changedir=testing commands = - pytest -n1 -rfsxX {posargs:testing} + pytest -n1 -rfsxX {posargs:.} -[testenv:py35-xdist] +[testenv:py36-xdist] deps = {[testenv:py27-xdist]deps} commands = pytest -n3 -rfsxX {posargs:testing} @@ -80,7 +80,7 @@ deps = pexpect commands = pytest -rfsxX test_pdb.py test_terminal.py test_unittest.py -[testenv:py35-pexpect] +[testenv:py36-pexpect] changedir = testing platform = linux|darwin deps = {[testenv:py27-pexpect]deps} @@ -92,17 +92,18 @@ deps = pytest-xdist>=1.13 hypothesis>=3.5.2 distribute = true +changedir=testing setenv = PYTHONDONTWRITEBYTECODE=1 commands = - pytest -n3 -rfsxX {posargs:testing} + pytest -n3 -rfsxX {posargs:.} [testenv:py27-trial] deps = twisted commands = pytest -ra {posargs:testing/test_unittest.py} -[testenv:py35-trial] +[testenv:py36-trial] deps = {[testenv:py27-trial]deps} commands = pytest -ra {posargs:testing/test_unittest.py} @@ -112,7 +113,7 @@ deps=numpy commands= pytest -rfsxX {posargs:testing/python/approx.py} -[testenv:py35-numpy] +[testenv:py36-numpy] deps=numpy commands= pytest -rfsxX {posargs:testing/python/approx.py} @@ -180,7 +181,6 @@ commands = [testenv:coveralls] passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH COVERALLS_REPO_TOKEN usedevelop = True -basepython = python3.5 changedir = . deps = {[testenv]deps}