Skip to content

Merge master into features #6691

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c9eeafa
Fix favicon for Chrome and Opera (#6639)
hugovk Jan 31, 2020
a9c5d31
PyCollector._genfunctions: use already created fixtureinfo (#6636)
blueyed Feb 1, 2020
8bd612b
typing: wrap_session
bluetech Feb 2, 2020
99d162e
Handle `Exit` exception in `pytest_sessionfinish`
blueyed Feb 2, 2020
c55bf23
doc: s/pytest_mark/pytestmark (#6661)
rebecca-palmer Feb 3, 2020
fb28966
Remove testing/test_modimport.py
blueyed Feb 3, 2020
abffd16
Keep (revisited) comment from https://github.com/pytest-dev/pytest/co…
blueyed Feb 3, 2020
1480aa3
Explicitly state on the PR template that we can squash commits (#6662)
nicoddemus Feb 3, 2020
b0d4526
internal: clean up getfslineno
blueyed Feb 2, 2020
61f2a26
Code/getfslineno: keep empty co_filename
blueyed Feb 3, 2020
dab90ef
typing: fix getfslineno
blueyed Feb 3, 2020
9c7f1d9
Remove compat.getfslineno
blueyed Feb 4, 2020
aa03287
assertion: save/restore hooks on item (#6646)
blueyed Feb 4, 2020
4316fe8
testing/conftest.py: testdir: set PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 (#…
blueyed Feb 4, 2020
bc49466
Remove testing/test_modimport.py (#6666)
blueyed Feb 4, 2020
632800a
internal: clean up getfslineno (#6656)
blueyed Feb 4, 2020
cdc7e13
pytester: clarify _makefile signature (#6675)
blueyed Feb 5, 2020
ef437ea
Remove incorrect choices comment (#6677)
rana-ahmed Feb 5, 2020
a8fc056
Handle `Exit` exception in `pytest_sessionfinish` (#6660)
blueyed Feb 6, 2020
23f14d5
Merge master into features
blueyed Feb 8, 2020
c0f0849
ci: Travis: upgrade pip
blueyed Feb 8, 2020
32a0920
ci: Travis: tox -vv
blueyed Feb 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Here is a quick checklist that should be present in PRs.
- [ ] Target the `features` branch for new features, improvements, and removals/deprecations.
- [ ] Include documentation when adding new features.
- [ ] Include new tests or update existing tests when applicable.
- [X] Allow maintainers to push and squash when merging my commits. Please uncheck this if you prefer to squash the commits yourself.

Unless your change is trivial or a small documentation fix (e.g., a typo or reword of a small section) please:

Expand Down
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ git:
depth: false

install:
- python -m pip install --upgrade pip
- python -m pip install --upgrade --pre tox

jobs:
Expand Down Expand Up @@ -61,7 +62,7 @@ before_script:
export _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess
fi

script: tox
script: tox -vv

after_success:
- |
Expand Down
1 change: 1 addition & 0 deletions changelog/6646.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Assertion rewriting hooks are (re)stored for the current item, which fixes them being still used after e.g. pytester's :func:`testdir.runpytest <_pytest.pytester.Testdir.runpytest>` etc.
1 change: 1 addition & 0 deletions changelog/6660.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:func:`pytest.exit() <_pytest.outcomes.exit>` is handled when emitted from the :func:`pytest_sessionfinish <_pytest.hookspec.pytest_sessionfinish>` hook. This includes quitting from a debugger.
2 changes: 1 addition & 1 deletion doc/en/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
html_favicon = "img/pytest1favi.ico"
html_favicon = "img/favicon.png"

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
Expand Down
Binary file added doc/en/img/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed doc/en/img/pytest1favi.ico
Binary file not shown.
4 changes: 2 additions & 2 deletions doc/en/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -901,8 +901,8 @@ Can be either a ``str`` or ``Sequence[str]``.
pytest_plugins = ("myapp.testsupport.tools", "myapp.testsupport.regression")


pytest_mark
~~~~~~~~~~~
pytestmark
~~~~~~~~~~

**Tutorial**: :ref:`scoped-marking`

Expand Down
2 changes: 2 additions & 0 deletions src/_pytest/_code/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ def path(self) -> Union[py.path.local, str]:
""" return a path object pointing to source code (or a str in case
of OSError / non-existing file).
"""
if not self.raw.co_filename:
return ""
try:
p = py.path.local(self.raw.co_filename)
# maybe don't try this checking
Expand Down
19 changes: 13 additions & 6 deletions src/_pytest/_code/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from bisect import bisect_right
from types import CodeType
from types import FrameType
from typing import Any
from typing import Iterator
from typing import List
from typing import Optional
Expand All @@ -17,6 +18,7 @@

import py

from _pytest.compat import get_real_func
from _pytest.compat import overload
from _pytest.compat import TYPE_CHECKING

Expand Down Expand Up @@ -277,14 +279,21 @@ def compile_( # noqa: F811
return s.compile(filename, mode, flags, _genframe=_genframe)


def getfslineno(obj) -> Tuple[Optional[Union["Literal['']", py.path.local]], int]:
def getfslineno(obj: Any) -> Tuple[Union[str, py.path.local], int]:
""" Return source location (path, lineno) for the given object.
If the source cannot be determined return ("", -1).

The line number is 0-based.
"""
from .code import Code

# xxx let decorators etc specify a sane ordering
# NOTE: this used to be done in _pytest.compat.getfslineno, initially added
# in 6ec13a2b9. It ("place_as") appears to be something very custom.
obj = get_real_func(obj)
if hasattr(obj, "place_as"):
obj = obj.place_as

try:
code = Code(obj)
except TypeError:
Expand All @@ -293,18 +302,16 @@ def getfslineno(obj) -> Tuple[Optional[Union["Literal['']", py.path.local]], int
except TypeError:
return "", -1

fspath = fn and py.path.local(fn) or None
fspath = fn and py.path.local(fn) or ""
lineno = -1
if fspath:
try:
_, lineno = findsource(obj)
except IOError:
pass
return fspath, lineno
else:
fspath = code.path
lineno = code.firstlineno
assert isinstance(lineno, int)
return fspath, lineno
return code.path, code.firstlineno


#
Expand Down
10 changes: 6 additions & 4 deletions src/_pytest/assertion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from _pytest.assertion import truncate
from _pytest.assertion import util
from _pytest.compat import TYPE_CHECKING
from _pytest.config import hookimpl

if TYPE_CHECKING:
from _pytest.main import Session
Expand Down Expand Up @@ -105,7 +106,8 @@ def pytest_collection(session: "Session") -> None:
assertstate.hook.set_session(session)


def pytest_runtest_setup(item):
@hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_protocol(item):
"""Setup the pytest_assertrepr_compare and pytest_assertion_pass hooks

The newinterpret and rewrite modules will use util._reprcompare if
Expand Down Expand Up @@ -143,6 +145,7 @@ def callbinrepr(op, left, right):
return res
return None

saved_assert_hooks = util._reprcompare, util._assertion_pass
util._reprcompare = callbinrepr

if item.ihook.pytest_assertion_pass.get_hookimpls():
Expand All @@ -154,10 +157,9 @@ def call_assertion_pass_hook(lineno, orig, expl):

util._assertion_pass = call_assertion_pass_hook

yield

def pytest_runtest_teardown(item):
util._reprcompare = None
util._assertion_pass = None
util._reprcompare, util._assertion_pass = saved_assert_hooks


def pytest_sessionfinish(session):
Expand Down
11 changes: 0 additions & 11 deletions src/_pytest/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import attr
import py

import _pytest
from _pytest._io.saferepr import saferepr
from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME
Expand Down Expand Up @@ -308,16 +307,6 @@ def get_real_method(obj, holder):
return obj


def getfslineno(obj) -> Tuple[Union[str, py.path.local], int]:
# xxx let decorators etc specify a sane ordering
obj = get_real_func(obj)
if hasattr(obj, "place_as"):
obj = obj.place_as
fslineno = _pytest._code.getfslineno(obj)
assert isinstance(fslineno[1], int), obj
return fslineno


def getimfunc(func):
try:
return func.__func__
Expand Down
5 changes: 4 additions & 1 deletion src/_pytest/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
from pluggy import PluginManager

import _pytest._code
import _pytest.assertion
import _pytest.deprecated
import _pytest.hookspec # the extension point definitions
from .exceptions import PrintHelp
Expand Down Expand Up @@ -262,6 +261,8 @@ class PytestPluginManager(PluginManager):
"""

def __init__(self):
import _pytest.assertion

super().__init__("pytest")
# The objects are module objects, only used generically.
self._conftest_plugins = set() # type: Set[object]
Expand Down Expand Up @@ -895,6 +896,8 @@ def _consider_importhook(self, args: Sequence[str]) -> None:
ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
mode = getattr(ns, "assertmode", "plain")
if mode == "rewrite":
import _pytest.assertion

try:
hook = _pytest.assertion.install_importhook(self)
except SystemError:
Expand Down
2 changes: 1 addition & 1 deletion src/_pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
import _pytest
from _pytest._code.code import FormattedExcinfo
from _pytest._code.code import TerminalRepr
from _pytest._code.source import getfslineno
from _pytest._io import TerminalWriter
from _pytest.compat import _format_args
from _pytest.compat import _PytestWrapper
from _pytest.compat import get_real_func
from _pytest.compat import get_real_method
from _pytest.compat import getfslineno
from _pytest.compat import getfuncargnames
from _pytest.compat import getimfunc
from _pytest.compat import getlocation
Expand Down
30 changes: 20 additions & 10 deletions src/_pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import importlib
import os
import sys
from typing import Callable
from typing import Dict
from typing import FrozenSet
from typing import List
Expand All @@ -24,7 +25,7 @@
from _pytest.config import hookimpl
from _pytest.config import UsageError
from _pytest.fixtures import FixtureManager
from _pytest.outcomes import exit
from _pytest.outcomes import Exit
from _pytest.reports import CollectReport
from _pytest.runner import collect_one_node
from _pytest.runner import SetupState
Expand Down Expand Up @@ -198,7 +199,9 @@ def pytest_addoption(parser):
)


def wrap_session(config, doit):
def wrap_session(
config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]]
) -> Union[int, ExitCode]:
"""Skeleton command line program"""
session = Session.from_config(config)
session.exitstatus = ExitCode.OK
Expand All @@ -215,10 +218,10 @@ def wrap_session(config, doit):
raise
except Failed:
session.exitstatus = ExitCode.TESTS_FAILED
except (KeyboardInterrupt, exit.Exception):
except (KeyboardInterrupt, Exit):
excinfo = _pytest._code.ExceptionInfo.from_current()
exitstatus = ExitCode.INTERRUPTED
if isinstance(excinfo.value, exit.Exception):
exitstatus = ExitCode.INTERRUPTED # type: Union[int, ExitCode]
if isinstance(excinfo.value, Exit):
if excinfo.value.returncode is not None:
exitstatus = excinfo.value.returncode
if initstate < 2:
Expand All @@ -232,7 +235,7 @@ def wrap_session(config, doit):
excinfo = _pytest._code.ExceptionInfo.from_current()
try:
config.notify_exception(excinfo, config.option)
except exit.Exception as exc:
except Exit as exc:
if exc.returncode is not None:
session.exitstatus = exc.returncode
sys.stderr.write("{}: {}\n".format(type(exc).__name__, exc))
Expand All @@ -241,12 +244,18 @@ def wrap_session(config, doit):
sys.stderr.write("mainloop: caught unexpected SystemExit!\n")

finally:
excinfo = None # Explicitly break reference cycle.
# Explicitly break reference cycle.
excinfo = None # type: ignore
session.startdir.chdir()
if initstate >= 2:
config.hook.pytest_sessionfinish(
session=session, exitstatus=session.exitstatus
)
try:
config.hook.pytest_sessionfinish(
session=session, exitstatus=session.exitstatus
)
except Exit as exc:
if exc.returncode is not None:
session.exitstatus = exc.returncode
sys.stderr.write("{}: {}\n".format(type(exc).__name__, exc))
config._ensure_unconfigure()
return session.exitstatus

Expand Down Expand Up @@ -386,6 +395,7 @@ class Session(nodes.FSCollector):
_setupstate = None # type: SetupState
# Set on the session by fixtures.pytest_sessionstart.
_fixturemanager = None # type: FixtureManager
exitstatus = None # type: Union[int, ExitCode]

def __init__(self, config: Config) -> None:
nodes.FSCollector.__init__(
Expand Down
2 changes: 1 addition & 1 deletion src/_pytest/mark/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

import attr

from .._code.source import getfslineno
from ..compat import ascii_escaped
from ..compat import ATTRS_EQ_FIELD
from ..compat import getfslineno
from ..compat import NOTSET
from _pytest.outcomes import fail
from _pytest.warning_types import PytestUnknownMarkWarning
Expand Down
2 changes: 1 addition & 1 deletion src/_pytest/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
from _pytest._code.code import ExceptionChainRepr
from _pytest._code.code import ExceptionInfo
from _pytest._code.code import ReprExceptionInfo
from _pytest._code.source import getfslineno
from _pytest.compat import cached_property
from _pytest.compat import getfslineno
from _pytest.compat import TYPE_CHECKING
from _pytest.config import Config
from _pytest.config import PytestPluginManager
Expand Down
8 changes: 4 additions & 4 deletions src/_pytest/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -610,14 +610,14 @@ def chdir(self):
"""
self.tmpdir.chdir()

def _makefile(self, ext, args, kwargs, encoding="utf-8"):
items = list(kwargs.items())
def _makefile(self, ext, lines, files, encoding="utf-8"):
items = list(files.items())

def to_text(s):
return s.decode(encoding) if isinstance(s, bytes) else str(s)

if args:
source = "\n".join(to_text(x) for x in args)
if lines:
source = "\n".join(to_text(x) for x in lines)
basename = self.request.function.__name__
items.insert(0, (basename, source))

Expand Down
4 changes: 2 additions & 2 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
from _pytest import nodes
from _pytest._code import filter_traceback
from _pytest._code.code import ExceptionInfo
from _pytest._code.source import getfslineno
from _pytest.compat import ascii_escaped
from _pytest.compat import get_default_arg_names
from _pytest.compat import get_real_func
from _pytest.compat import getfslineno
from _pytest.compat import getimfunc
from _pytest.compat import getlocation
from _pytest.compat import is_generator
Expand Down Expand Up @@ -392,7 +392,7 @@ def _genfunctions(self, name, funcobj):
fm = self.session._fixturemanager

definition = FunctionDefinition.from_parent(self, name=name, callobj=funcobj)
fixtureinfo = fm.getfixtureinfo(definition, funcobj, cls)
fixtureinfo = definition._fixtureinfo

metafunc = Metafunc(
definition, fixtureinfo, self.config, cls=cls, module=module
Expand Down
2 changes: 2 additions & 0 deletions testing/acceptance_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ def test_option(pytestconfig):

@pytest.mark.parametrize("load_cov_early", [True, False])
def test_early_load_setuptools_name(self, testdir, monkeypatch, load_cov_early):
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")

testdir.makepyfile(mytestplugin1_module="")
testdir.makepyfile(mytestplugin2_module="")
testdir.makepyfile(mycov_module="")
Expand Down
8 changes: 8 additions & 0 deletions testing/code/test_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,14 @@ class B:
B.__name__ = "B2"
assert getfslineno(B)[1] == -1

co = compile("...", "", "eval")
assert co.co_filename == ""

if hasattr(sys, "pypy_version_info"):
assert getfslineno(co) == ("", -1)
else:
assert getfslineno(co) == ("", 0)


def test_code_of_object_instance_with_call() -> None:
class A:
Expand Down
Loading