From fc11b81005800fcc611edbea57c26b37561703db Mon Sep 17 00:00:00 2001 From: Jeffrey Rackauckas Date: Sun, 7 Oct 2018 19:19:48 -0700 Subject: [PATCH 01/26] Exclude durations that are 0.00 seconds long. --- changelog/4063.trivial.rst | 1 + src/_pytest/runner.py | 3 +++ testing/acceptance_test.py | 32 +++++++++++++++++++++++--------- 3 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 changelog/4063.trivial.rst diff --git a/changelog/4063.trivial.rst b/changelog/4063.trivial.rst new file mode 100644 index 00000000000..3e0aaf0f012 --- /dev/null +++ b/changelog/4063.trivial.rst @@ -0,0 +1 @@ +Exclude 0.00 second entries from ``--duration`` output. diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 1ba9ff310b7..8f1a710f9d3 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -30,6 +30,7 @@ def pytest_addoption(parser): def pytest_terminal_summary(terminalreporter): durations = terminalreporter.config.option.durations + verbose = terminalreporter.config.getvalue("verbose") if durations is None: return tr = terminalreporter @@ -49,6 +50,8 @@ def pytest_terminal_summary(terminalreporter): dlist = dlist[:durations] for rep in dlist: + if verbose < 2 and rep.duration < 0.01: + break nodeid = rep.nodeid.replace("::()::", "::") tr.write_line("%02.2fs %-8s %s" % (rep.duration, rep.when, nodeid)) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 332af27b5f2..9d76415dac0 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -816,7 +816,7 @@ def test_calls(self, testdir): result = testdir.runpytest("--durations=10") assert result.ret == 0 result.stdout.fnmatch_lines_random( - ["*durations*", "*call*test_3*", "*call*test_2*", "*call*test_1*"] + ["*durations*", "*call*test_3*", "*call*test_2*"] ) def test_calls_show_2(self, testdir): @@ -830,6 +830,18 @@ def test_calls_showall(self, testdir): testdir.makepyfile(self.source) result = testdir.runpytest("--durations=0") assert result.ret == 0 + for x in "23": + for y in ("call",): # 'setup', 'call', 'teardown': + for line in result.stdout.lines: + if ("test_%s" % x) in line and y in line: + break + else: + raise AssertionError("not found {} {}".format(x, y)) + + def test_calls_showall_verbose(self, testdir): + testdir.makepyfile(self.source) + result = testdir.runpytest("--durations=0", "-vv") + assert result.ret == 0 for x in "123": for y in ("call",): # 'setup', 'call', 'teardown': for line in result.stdout.lines: @@ -840,9 +852,9 @@ def test_calls_showall(self, testdir): def test_with_deselected(self, testdir): testdir.makepyfile(self.source) - result = testdir.runpytest("--durations=2", "-k test_1") + result = testdir.runpytest("--durations=2", "-k test_2") assert result.ret == 0 - result.stdout.fnmatch_lines(["*durations*", "*call*test_1*"]) + result.stdout.fnmatch_lines(["*durations*", "*call*test_2*"]) def test_with_failing_collection(self, testdir): testdir.makepyfile(self.source) @@ -862,13 +874,15 @@ def test_with_not(self, testdir): class TestDurationWithFixture(object): source = """ + import pytest import time - frag = 0.001 - def setup_function(func): - time.sleep(frag * 3) - def test_1(): - time.sleep(frag*2) - def test_2(): + frag = 0.01 + + @pytest.fixture + def setup_fixt(): + time.sleep(frag) + + def test_1(setup_fixt): time.sleep(frag) """ From 81426c3d196028d77fb1bcbcccca8b9d83ea79a3 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Wed, 10 Oct 2018 18:02:37 +0200 Subject: [PATCH 02/26] tests: harden test_cmdline_python_package_symlink --- testing/acceptance_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index b0d0a7e6100..fde25549da7 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -738,8 +738,8 @@ def join_pythonpath(*dirs): assert result.ret == 0 result.stdout.fnmatch_lines( [ - "*lib/foo/bar/test_bar.py::test_bar*PASSED*", - "*lib/foo/bar/test_bar.py::test_other*PASSED*", + "*lib/foo/bar/test_bar.py::test_bar PASSED*", + "*lib/foo/bar/test_bar.py::test_other PASSED*", "*2 passed*", ] ) From 6bf4692c7d8100b19dc0178764398edbd7803701 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 12 Oct 2018 15:15:35 +0200 Subject: [PATCH 03/26] acceptance_test: clarify/document/fix tests Ref: https://github.com/pytest-dev/pytest/commit/e2e6e317118d6d15ccdf6f6708a48c08d85cbc25#r30863971 --- testing/acceptance_test.py | 40 ++++++++++++++------------------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index fde25549da7..29b4f2b428e 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -12,6 +12,13 @@ from _pytest.main import EXIT_NOTESTSCOLLECTED, EXIT_USAGEERROR +def prepend_pythonpath(*dirs): + cur = os.getenv("PYTHONPATH") + if cur: + dirs += (cur,) + return os.pathsep.join(str(p) for p in dirs) + + class TestGeneralUsage(object): def test_config_error(self, testdir): testdir.copy_example("conftest_usageerror/conftest.py") @@ -570,14 +577,8 @@ def test_cmdline_python_package(self, testdir, monkeypatch): assert result.ret == 0 result.stdout.fnmatch_lines(["*1 passed*"]) - def join_pythonpath(what): - cur = os.environ.get("PYTHONPATH") - if cur: - return str(what) + os.pathsep + cur - return what - empty_package = testdir.mkpydir("empty_package") - monkeypatch.setenv("PYTHONPATH", str(join_pythonpath(empty_package))) + monkeypatch.setenv("PYTHONPATH", str(empty_package), prepend=os.pathsep) # the path which is not a package raises a warning on pypy; # no idea why only pypy and not normal python warn about it here with warnings.catch_warnings(): @@ -586,7 +587,7 @@ def join_pythonpath(what): assert result.ret == 0 result.stdout.fnmatch_lines(["*2 passed*"]) - monkeypatch.setenv("PYTHONPATH", str(join_pythonpath(testdir))) + monkeypatch.setenv("PYTHONPATH", str(testdir), prepend=os.pathsep) result = testdir.runpytest("--pyargs", "tpkg.test_missing", syspathinsert=True) assert result.ret != 0 result.stderr.fnmatch_lines(["*not*found*test_missing*"]) @@ -626,18 +627,13 @@ def test_cmdline_python_namespace_package(self, testdir, monkeypatch): # ├── __init__.py # └── test_world.py - def join_pythonpath(*dirs): - cur = os.environ.get("PYTHONPATH") - if cur: - dirs += (cur,) - return os.pathsep.join(str(p) for p in dirs) - - monkeypatch.setenv("PYTHONPATH", join_pythonpath(*search_path)) + # NOTE: the different/reversed ordering is intentional here. + monkeypatch.setenv("PYTHONPATH", prepend_pythonpath(*search_path)) for p in search_path: monkeypatch.syspath_prepend(p) # mixed module and filenames: - os.chdir("world") + monkeypatch.chdir("world") result = testdir.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world") assert result.ret == 0 result.stdout.fnmatch_lines( @@ -688,8 +684,6 @@ def test_cmdline_python_package_symlink(self, testdir, monkeypatch): pytest.skip(six.text_type(e.args[0])) monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False) - search_path = ["lib", os.path.join("local", "lib")] - dirname = "lib" d = testdir.mkdir(dirname) foo = d.mkdir("foo") @@ -722,13 +716,9 @@ def test_cmdline_python_package_symlink(self, testdir, monkeypatch): # ├── conftest.py # └── test_bar.py - def join_pythonpath(*dirs): - cur = os.getenv("PYTHONPATH") - if cur: - dirs += (cur,) - return os.pathsep.join(str(p) for p in dirs) - - monkeypatch.setenv("PYTHONPATH", join_pythonpath(*search_path)) + # NOTE: the different/reversed ordering is intentional here. + search_path = ["lib", os.path.join("local", "lib")] + monkeypatch.setenv("PYTHONPATH", prepend_pythonpath(*search_path)) for p in search_path: monkeypatch.syspath_prepend(p) From 10ddc466bf8cd221a2c088ff2064cd058b51d9a1 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 12 Oct 2018 16:52:13 +0200 Subject: [PATCH 04/26] minor: typo and code style --- src/_pytest/fixtures.py | 5 ++--- src/_pytest/main.py | 7 ++----- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index ffaa1b09a71..21de82b02d4 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1013,7 +1013,7 @@ class FixtureFunctionMarker(object): def __call__(self, function): if isclass(function): - raise ValueError("class fixtures not supported (may be in the future)") + raise ValueError("class fixtures not supported (maybe in the future)") if getattr(function, "_pytestfixturefunction", False): raise ValueError( @@ -1366,8 +1366,7 @@ def getfixturedefs(self, argname, nodeid): fixturedefs = self._arg2fixturedefs[argname] except KeyError: return None - else: - return tuple(self._matchfactories(fixturedefs, nodeid)) + return tuple(self._matchfactories(fixturedefs, nodeid)) def _matchfactories(self, fixturedefs, nodeid): for fixturedef in fixturedefs: diff --git a/src/_pytest/main.py b/src/_pytest/main.py index ce07285a41a..5ad91dfc47c 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -564,9 +564,7 @@ def _recurse(self, path): return True def _tryconvertpyarg(self, x): - """Convert a dotted module name to path. - - """ + """Convert a dotted module name to path.""" try: with _patched_find_module(): loader = pkgutil.find_loader(x) @@ -598,8 +596,7 @@ def _parsearg(self, arg): raise UsageError( "file or package not found: " + arg + " (missing __init__.py?)" ) - else: - raise UsageError("file not found: " + arg) + raise UsageError("file not found: " + arg) parts[0] = path return parts From ee54fb9a6b3d64224059201e6d3a2a238961e6e6 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 13 Oct 2018 17:01:22 +0200 Subject: [PATCH 05/26] pytester: use EXIT_INTERRUPTED --- src/_pytest/pytester.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index a5099917240..e3bf060e3b0 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -19,7 +19,7 @@ from _pytest._code import Source import py import pytest -from _pytest.main import Session, EXIT_OK +from _pytest.main import Session, EXIT_INTERRUPTED, EXIT_OK from _pytest.assertion.rewrite import AssertionRewritingHook from _pytest.compat import Path from _pytest.compat import safe_str @@ -845,7 +845,7 @@ class reprec(object): # typically we reraise keyboard interrupts from the child run # because it's our user requesting interruption of the testing - if ret == 2 and not kwargs.get("no_reraise_ctrlc"): + if ret == EXIT_INTERRUPTED and not kwargs.get("no_reraise_ctrlc"): calls = reprec.getcalls("pytest_keyboard_interrupt") if calls and calls[-1].excinfo.type == KeyboardInterrupt: raise KeyboardInterrupt() From 91b279749835a0dd2130cb2e6c54e4ab3b4d8848 Mon Sep 17 00:00:00 2001 From: Thomas Hess Date: Sat, 13 Oct 2018 20:33:31 +0200 Subject: [PATCH 06/26] docs: deprecation.rst: Add missing arguments to code example In the proposed new style using `@pytest.mark.parametrize`, the example function signature missed the actual arguments. Add the missing arguments --- doc/en/deprecations.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index a9d153206b9..14eeeb08f13 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -243,7 +243,7 @@ This form of test function doesn't support fixtures properly, and users should s .. code-block:: python @pytest.mark.parametrize("x, y", [(2, 4), (3, 9)]) - def test_squared(): + def test_squared(x, y): assert x ** x == y From 47f5c2900263c635e8159dd1050754a7504270d5 Mon Sep 17 00:00:00 2001 From: Jeffrey Rackauckas Date: Sat, 13 Oct 2018 12:51:04 -0700 Subject: [PATCH 07/26] Update messaging for --durations when not in verbose mode. --- changelog/4063.trivial.rst | 2 +- doc/en/usage.rst | 1 + src/_pytest/runner.py | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog/4063.trivial.rst b/changelog/4063.trivial.rst index 3e0aaf0f012..46535640175 100644 --- a/changelog/4063.trivial.rst +++ b/changelog/4063.trivial.rst @@ -1 +1 @@ -Exclude 0.00 second entries from ``--duration`` output. +Exclude 0.00 second entries from ``--duration`` output unless ``-vv`` is passed on the command-line. diff --git a/doc/en/usage.rst b/doc/en/usage.rst index e5521bba592..cc118fad854 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -269,6 +269,7 @@ To get a list of the slowest 10 test durations:: pytest --durations=10 +By default, pytest will not show test durations that are too small (<0.01s) unless ``-vv`` is passed on the command-line. Creating JUnitXML format files ---------------------------------------------------- diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 8f1a710f9d3..f01b06314e7 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -51,6 +51,7 @@ def pytest_terminal_summary(terminalreporter): for rep in dlist: if verbose < 2 and rep.duration < 0.01: + tr.write_line("0.00 durations hidden. Use -vv to show these durations.") break nodeid = rep.nodeid.replace("::()::", "::") tr.write_line("%02.2fs %-8s %s" % (rep.duration, rep.when, nodeid)) From 7a271a91b09e39889775bbc94718d7fb023a1bfa Mon Sep 17 00:00:00 2001 From: Jeffrey Rackauckas Date: Sat, 13 Oct 2018 12:55:17 -0700 Subject: [PATCH 08/26] Fix rounding error when displaying durations in non-verbose mode. --- src/_pytest/runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index f01b06314e7..654bd4f2286 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -50,7 +50,7 @@ def pytest_terminal_summary(terminalreporter): dlist = dlist[:durations] for rep in dlist: - if verbose < 2 and rep.duration < 0.01: + if verbose < 2 and rep.duration < 0.005: tr.write_line("0.00 durations hidden. Use -vv to show these durations.") break nodeid = rep.nodeid.replace("::()::", "::") From d3d8d53e413d36eb12b387c404c65e6ce18805b1 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 13 Oct 2018 16:42:33 +0200 Subject: [PATCH 09/26] tests: test_pdb: fix print statements --- testing/test_pdb.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 4739f0e2dca..f04a2f3a0ff 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -345,7 +345,7 @@ def test_pdb_interaction_capturing_simple(self, testdir): import pytest def test_1(): i = 0 - print ("hello17") + print("hello17") pytest.set_trace() x = 3 """ @@ -383,7 +383,7 @@ def test_pdb_and_capsys(self, testdir): """ import pytest def test_1(capsys): - print ("hello1") + print("hello1") pytest.set_trace() """ ) @@ -420,7 +420,7 @@ def test_set_trace_capturing_afterwards(self, testdir): def test_1(): pdb.set_trace() def test_2(): - print ("hello") + print("hello") assert 0 """ ) @@ -461,10 +461,10 @@ def test_pdb_interaction_capturing_twice(self, testdir): import pytest def test_1(): i = 0 - print ("hello17") + print("hello17") pytest.set_trace() x = 3 - print ("hello18") + print("hello18") pytest.set_trace() x = 4 """ @@ -525,7 +525,7 @@ def test_enter_pdb_hook_is_called(self, testdir): """ def pytest_enter_pdb(config): assert config.testing_verification == 'configured' - print 'enter_pdb_hook' + print('enter_pdb_hook') def pytest_configure(config): config.testing_verification = 'configured' @@ -562,7 +562,7 @@ def test_pdb_custom_cls_with_settrace(self, testdir, monkeypatch): custom_pdb=""" class CustomPdb(object): def set_trace(*args, **kwargs): - print 'custom set_trace>' + print('custom set_trace>') """ ) p1 = testdir.makepyfile( From 3683d92c530472cab40d74d9e8c951b33f9ef411 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 13 Oct 2018 18:36:20 -0300 Subject: [PATCH 10/26] Adjust the 'durations hidden' message --- src/_pytest/runner.py | 3 ++- testing/acceptance_test.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 654bd4f2286..05731799333 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -51,7 +51,8 @@ def pytest_terminal_summary(terminalreporter): for rep in dlist: if verbose < 2 and rep.duration < 0.005: - tr.write_line("0.00 durations hidden. Use -vv to show these durations.") + tr.write_line("") + tr.write_line("(0.00 durations hidden. Use -vv to show these durations.)") break nodeid = rep.nodeid.replace("::()::", "::") tr.write_line("%02.2fs %-8s %s" % (rep.duration, rep.when, nodeid)) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 9d76415dac0..59d3c012ac5 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -818,6 +818,10 @@ def test_calls(self, testdir): result.stdout.fnmatch_lines_random( ["*durations*", "*call*test_3*", "*call*test_2*"] ) + assert "test_something" not in result.stdout.str() + result.stdout.fnmatch_lines( + ["(0.00 durations hidden. Use -vv to show these durations.)"] + ) def test_calls_show_2(self, testdir): testdir.makepyfile(self.source) From 448830e6566735919068b34e5e40abccf05ff524 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 13 Oct 2018 16:33:53 +0200 Subject: [PATCH 11/26] Do not print INTERNALERROR with --pdb This gets printed by the terminal reporter already, and currently results in the same error being displayed twice, e.g. when raising an `Exception` manually from `pytest.debugging.pytest_exception_interact`. --- changelog/4132.bugfix.rst | 1 + src/_pytest/debugging.py | 3 --- testing/test_pdb.py | 6 +++++- 3 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 changelog/4132.bugfix.rst diff --git a/changelog/4132.bugfix.rst b/changelog/4132.bugfix.rst new file mode 100644 index 00000000000..1fbb9afad4c --- /dev/null +++ b/changelog/4132.bugfix.rst @@ -0,0 +1 @@ +Fix duplicate printing of internal errors when using ``--pdb``. diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index f51dff373c9..d9a6a8154cc 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -109,9 +109,6 @@ def pytest_exception_interact(self, node, call, report): _enter_pdb(node, call.excinfo, report) def pytest_internalerror(self, excrepr, excinfo): - for line in str(excrepr).split("\n"): - sys.stderr.write("INTERNALERROR> %s\n" % line) - sys.stderr.flush() tb = _postmortem_traceback(excinfo) post_mortem(tb) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index f04a2f3a0ff..cda553c446c 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -334,8 +334,12 @@ def pytest_runtest_protocol(): ) p1 = testdir.makepyfile("def test_func(): pass") child = testdir.spawn_pytest("--pdb %s" % p1) - # child.expect(".*import pytest.*") child.expect("Pdb") + + # INTERNALERROR is only displayed once via terminal reporter. + assert len([x for x in child.before.decode().splitlines() + if x.startswith("INTERNALERROR> Traceback")]) == 1 + child.sendeof() self.flush(child) From 86c7dcff689ecb72a27f6d9dd48abf56bb9d969d Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sat, 13 Oct 2018 16:49:30 +0200 Subject: [PATCH 12/26] pdb: handle quitting in post_mortem `help quit` in pdb says: > Quit from the debugger. The program being executed is aborted. But pytest would continue with the next tests, often making it necessary to kill the pytest process when using `--pdb` and trying to cancel the tests using `KeyboardInterrupt` / `Ctrl-C`. --- src/_pytest/debugging.py | 8 ++++++-- testing/test_pdb.py | 15 ++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index f51dff373c9..325cd60ddd8 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -1,10 +1,12 @@ """ interactive debugging with PDB, the Python Debugger. """ from __future__ import absolute_import, division, print_function + +import os import pdb import sys -import os from doctest import UnexpectedException +from _pytest import outcomes from _pytest.config import hookimpl try: @@ -164,8 +166,9 @@ def _enter_pdb(node, excinfo, rep): rep.toterminal(tw) tw.sep(">", "entering PDB") tb = _postmortem_traceback(excinfo) - post_mortem(tb) rep._pdbshown = True + if post_mortem(tb): + outcomes.exit("Quitting debugger") return rep @@ -196,3 +199,4 @@ def get_stack(self, f, t): p = Pdb() p.reset() p.interaction(None, t) + return p.quitting diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 4739f0e2dca..4a5e740f7b1 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -25,6 +25,8 @@ def custom_pdb_calls(): # install dummy debugger class and track which methods were called on it class _CustomPdb(object): + quitting = False + def __init__(self, *args, **kwargs): called.append("init") @@ -142,6 +144,9 @@ def test_pdb_interaction(self, testdir): def test_1(): i = 0 assert i == 1 + + def test_not_called_due_to_quit(): + pass """ ) child = testdir.spawn_pytest("--pdb %s" % p1) @@ -150,8 +155,9 @@ def test_1(): child.expect("Pdb") child.sendeof() rest = child.read().decode("utf8") - assert "1 failed" in rest + assert "= 1 failed in" in rest assert "def test_1" not in rest + assert "Exit: Quitting debugger" in rest self.flush(child) @staticmethod @@ -321,7 +327,7 @@ def test_pdb_interaction_on_collection_issue181(self, testdir): child = testdir.spawn_pytest("--pdb %s" % p1) # child.expect(".*import pytest.*") child.expect("Pdb") - child.sendeof() + child.sendline("c") child.expect("1 error") self.flush(child) @@ -376,6 +382,7 @@ def test_1(): rest = child.read().decode("utf8") assert "1 failed" in rest assert "reading from stdin while output" not in rest + assert "BdbQuit" in rest self.flush(child) def test_pdb_and_capsys(self, testdir): @@ -518,7 +525,9 @@ def test_foo(a): def test_pdb_collection_failure_is_shown(self, testdir): p1 = testdir.makepyfile("xxx") result = testdir.runpytest_subprocess("--pdb", p1) - result.stdout.fnmatch_lines(["*NameError*xxx*", "*1 error*"]) + result.stdout.fnmatch_lines( + ["E NameError: *xxx*", "*! *Exit: Quitting debugger !*"] # due to EOF + ) def test_enter_pdb_hook_is_called(self, testdir): testdir.makeconftest( From 4a49715614a015b18f33ce442e23bdab4f584750 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 14 Oct 2018 00:07:40 +0200 Subject: [PATCH 13/26] tox.ini: pexpect: use posargs; cleanup posargs - no need for {posargs:testing} really - remove `-ra`, used with addopts already --- tox.ini | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tox.ini b/tox.ini index 1c77f989d24..3372ee2073e 100644 --- a/tox.ini +++ b/tox.ini @@ -18,7 +18,7 @@ envlist = [testenv] commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --lsof -ra {posargs:testing} + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --lsof coverage: coverage combine coverage: coverage report passenv = USER USERNAME @@ -43,7 +43,7 @@ deps = nose passenv = USER USERNAME TRAVIS commands = - pytest -n auto -ra --runpytest=subprocess {posargs:testing} + pytest -n auto --runpytest=subprocess [testenv:linting] @@ -61,7 +61,7 @@ deps = {env:_PYTEST_TOX_EXTRA_DEP:} passenv = USER USERNAME TRAVIS commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto -ra {posargs:testing} + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto [testenv:py36-xdist] # NOTE: copied from above due to https://github.com/tox-dev/tox/issues/706. @@ -80,7 +80,7 @@ deps = pexpect {env:_PYTEST_TOX_EXTRA_DEP:} commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -ra test_pdb.py test_terminal.py test_unittest.py + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest test_pdb.py test_terminal.py test_unittest.py {posargs} [testenv:py36-pexpect] changedir = {[testenv:py27-pexpect]changedir} @@ -101,14 +101,14 @@ setenv = PYTHONDONTWRITEBYTECODE=1 passenv = USER USERNAME TRAVIS commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto -ra {posargs:.} + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {posargs:.} [testenv:py27-trial] deps = twisted {env:_PYTEST_TOX_EXTRA_DEP:} commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -ra {posargs:testing/test_unittest.py} + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/test_unittest.py} [testenv:py36-trial] deps = {[testenv:py27-trial]deps} @@ -119,7 +119,7 @@ deps = numpy {env:_PYTEST_TOX_EXTRA_DEP:} commands= - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -ra {posargs:testing/python/approx.py} + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/python/approx.py} [testenv:py36-numpy] deps = {[testenv:py27-numpy]deps} @@ -154,7 +154,7 @@ deps = PyYAML {env:_PYTEST_TOX_EXTRA_DEP:} commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -ra doc/en + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest doc/en {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest [testenv:regen] @@ -175,7 +175,7 @@ commands = [testenv:jython] changedir = testing commands = - {envpython} {envbindir}/py.test-jython -ra {posargs} + {envpython} {envbindir}/py.test-jython {posargs} [testenv:py36-freeze] changedir = testing/freeze From e3bf9cede4151c930092f68e7088a3e1a379241b Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 13 Oct 2018 22:13:25 -0300 Subject: [PATCH 14/26] Fix linting --- testing/test_pdb.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index cda553c446c..6b9625b55da 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -337,8 +337,16 @@ def pytest_runtest_protocol(): child.expect("Pdb") # INTERNALERROR is only displayed once via terminal reporter. - assert len([x for x in child.before.decode().splitlines() - if x.startswith("INTERNALERROR> Traceback")]) == 1 + assert ( + len( + [ + x + for x in child.before.decode().splitlines() + if x.startswith("INTERNALERROR> Traceback") + ] + ) + == 1 + ) child.sendeof() self.flush(child) From 7c380b19f393ed4f71d66e6d57439b5d59951571 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 14 Oct 2018 12:31:43 +0200 Subject: [PATCH 15/26] tox.ini: passenv: COVERAGE_* This is required to pass through COVERAGE_PROCESS_START etc. --- tox.ini | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tox.ini b/tox.ini index 3372ee2073e..0b2cba40717 100644 --- a/tox.ini +++ b/tox.ini @@ -21,7 +21,7 @@ commands = {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --lsof coverage: coverage combine coverage: coverage report -passenv = USER USERNAME +passenv = USER USERNAME COVERAGE_* setenv = # configuration if a user runs tox with a "coverage" factor, for example "tox -e py36-coverage" coverage: _PYTEST_TOX_COVERAGE_RUN=coverage run -m @@ -41,7 +41,6 @@ deps = pytest-xdist>=1.13 py27: mock nose -passenv = USER USERNAME TRAVIS commands = pytest -n auto --runpytest=subprocess @@ -59,7 +58,6 @@ deps = nose hypothesis>=3.56 {env:_PYTEST_TOX_EXTRA_DEP:} -passenv = USER USERNAME TRAVIS commands = {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto @@ -99,7 +97,6 @@ changedir=testing setenv = {[testenv]setenv} PYTHONDONTWRITEBYTECODE=1 -passenv = USER USERNAME TRAVIS commands = {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {posargs:.} From 6d0667f1dba390d7692d2c8157960a6f747ce291 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 14 Oct 2018 14:23:09 +0200 Subject: [PATCH 16/26] CI: run specialized factors in a single job Given the setup time for jobs, it makes sense to run *-pexpect,*-trial,*-numpy in a single build job. --- .travis.yml | 14 +++++--------- appveyor.yml | 15 ++++++--------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3936e599b71..5c479feed9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,19 +12,15 @@ install: - pip install --upgrade --pre tox env: matrix: - # note: please use "tox --listenvs" to populate the build matrix below - # please remove the linting env in all cases - - TOXENV=py27-pexpect + # Specialized factors for py27. + - TOXENV=py27-pexpect,py27-trial,py27-numpy + - TOXENV=py27-nobyte - TOXENV=py27-xdist - - TOXENV=py27-trial - - TOXENV=py27-numpy - TOXENV=py27-pluggymaster PYTEST_NO_COVERAGE=1 - - TOXENV=py36-pexpect + # Specialized factors for py36. + - TOXENV=py36-pexpect,py36-trial,py36-numpy - TOXENV=py36-xdist - - TOXENV=py36-trial - - TOXENV=py36-numpy - TOXENV=py36-pluggymaster PYTEST_NO_COVERAGE=1 - - TOXENV=py27-nobyte jobs: include: diff --git a/appveyor.yml b/appveyor.yml index efc8806382e..c8329c71757 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,5 @@ environment: matrix: - - TOXENV: "linting,docs,doctesting" - PYTEST_NO_COVERAGE: "1" - TOXENV: "py27" - TOXENV: "py34" - TOXENV: "py35" @@ -9,19 +7,18 @@ environment: - TOXENV: "py37" - TOXENV: "pypy" PYTEST_NO_COVERAGE: "1" - - TOXENV: "py27-xdist" - - TOXENV: "py27-trial" - - TOXENV: "py27-numpy" + # Specialized factors for py27. + - TOXENV: "py27-trial,py27-numpy,py27-nobyte" - TOXENV: "py27-pluggymaster" PYTEST_NO_COVERAGE: "1" - - TOXENV: "py36-xdist" - - TOXENV: "py36-trial" - - TOXENV: "py36-numpy" + - TOXENV: "py27-xdist" + # Specialized factors for py36. + - TOXENV: "py36-trial,py36-numpy" - TOXENV: "py36-pluggymaster" PYTEST_NO_COVERAGE: "1" - - TOXENV: "py27-nobyte" - TOXENV: "py36-freeze" PYTEST_NO_COVERAGE: "1" + - TOXENV: "py36-xdist" install: - echo Installed Pythons From 7ded937e19c4ceb5332dc9719b88ef8fcb78feb5 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 14 Oct 2018 14:24:28 +0200 Subject: [PATCH 17/26] AppVeyor: use fast_finish This runs py27, py37 and linting first - simulating the baseline stage used on Travis. --- appveyor.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index c8329c71757..abe4319841c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,10 +1,12 @@ environment: matrix: - TOXENV: "py27" - - TOXENV: "py34" - - TOXENV: "py35" - - TOXENV: "py36" - TOXENV: "py37" + PYTEST_NO_COVERAGE: "1" + - TOXENV: "linting,docs,doctesting" + - TOXENV: "py36" + - TOXENV: "py35" + - TOXENV: "py34" - TOXENV: "pypy" PYTEST_NO_COVERAGE: "1" # Specialized factors for py27. @@ -20,6 +22,9 @@ environment: PYTEST_NO_COVERAGE: "1" - TOXENV: "py36-xdist" +matrix: + fast_finish: true + install: - echo Installed Pythons - dir c:\Python* From 3bd9f981a2d1e6b62e7d0c7ac03bca79c4877b3a Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Sun, 14 Oct 2018 12:02:35 +0200 Subject: [PATCH 18/26] tox.ini: clean up changedir --- tox.ini | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tox.ini b/tox.ini index 3372ee2073e..123fb2fde5f 100644 --- a/tox.ini +++ b/tox.ini @@ -36,7 +36,6 @@ deps = {env:_PYTEST_TOX_EXTRA_DEP:} [testenv:py27-subprocess] -changedir = . deps = pytest-xdist>=1.13 py27: mock @@ -74,16 +73,14 @@ deps = commands = {[testenv:py27-xdist]commands} [testenv:py27-pexpect] -changedir = testing platform = linux|darwin deps = pexpect {env:_PYTEST_TOX_EXTRA_DEP:} commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest test_pdb.py test_terminal.py test_unittest.py {posargs} + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest testing/test_pdb.py testing/test_terminal.py testing/test_unittest.py {posargs} [testenv:py36-pexpect] -changedir = {[testenv:py27-pexpect]changedir} platform = {[testenv:py27-pexpect]platform} deps = {[testenv:py27-pexpect]deps} commands = {[testenv:py27-pexpect]commands} @@ -95,13 +92,12 @@ deps = py27: mock {env:_PYTEST_TOX_EXTRA_DEP:} distribute = true -changedir=testing setenv = {[testenv]setenv} PYTHONDONTWRITEBYTECODE=1 passenv = USER USERNAME TRAVIS commands = - {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {posargs:.} + {env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {posargs} [testenv:py27-trial] deps = From 486ded3fcae5aac100b49e49ef55192053e15807 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 14 Oct 2018 12:22:56 -0300 Subject: [PATCH 19/26] Fix flaky durations test Unfortunately due to fluctuations in runtime "test_something" might still appear in the final message. Example failure: https://ci.appveyor.com/project/pytestbot/pytest/builds/19494829/job/8lx847u0c78m63wf --- testing/acceptance_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 6f791dc84a4..41cdba0671a 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -808,7 +808,6 @@ def test_calls(self, testdir): result.stdout.fnmatch_lines_random( ["*durations*", "*call*test_3*", "*call*test_2*"] ) - assert "test_something" not in result.stdout.str() result.stdout.fnmatch_lines( ["(0.00 durations hidden. Use -vv to show these durations.)"] ) From 48081458466f1ae62766be7593cc9b9e682ddbef Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 14 Oct 2018 15:17:08 -0300 Subject: [PATCH 20/26] test_request_garbage is flaky when running with xdist Example failure: https://travis-ci.org/pytest-dev/pytest/jobs/441305926#L545 --- testing/python/fixture.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 7e125e0b76b..7ec7e9c1cc2 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -494,6 +494,12 @@ def test_method(self, something): reason="this method of test doesn't work on pypy", ) def test_request_garbage(self, testdir): + try: + import xdist # noqa + except ImportError: + pass + else: + pytest.xfail("this test is flaky when executed with xdist") testdir.makepyfile( """ import sys From 2e42d937dcbfc17398210e957e054e815c694ca2 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 14 Oct 2018 16:11:47 -0700 Subject: [PATCH 21/26] Display the filename when encountering `SyntaxWarning`. ```console $ cd t && rm -rf __pycache__ && pytest t.py -q -c /dev/null; cd .. . [100%] =============================== warnings summary =============================== :2: DeprecationWarning: invalid escape sequence \. -- Docs: https://docs.pytest.org/en/latest/warnings.html 1 passed, 1 warnings in 0.01 seconds ``` ```console $ cd t && rm -rf __pycache__ && pytest t.py -q -c /dev/null; cd .. . [100%] =============================== warnings summary =============================== /tmp/pytest/t/t.py:2: DeprecationWarning: invalid escape sequence \. '\.wat' -- Docs: https://docs.pytest.org/en/latest/warnings.html 1 passed, 1 warnings in 0.01 seconds ``` --- changelog/4152.bugfix.rst | 1 + src/_pytest/assertion/rewrite.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog/4152.bugfix.rst diff --git a/changelog/4152.bugfix.rst b/changelog/4152.bugfix.rst new file mode 100644 index 00000000000..ce254729454 --- /dev/null +++ b/changelog/4152.bugfix.rst @@ -0,0 +1 @@ +Display the filename when encountering ``SyntaxWarning``. diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 7a11c4ec18d..385ae4e7797 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -398,7 +398,7 @@ def _rewrite_test(config, fn): finally: del state._indecode try: - tree = ast.parse(source) + tree = ast.parse(source, filename=fn.strpath) except SyntaxError: # Let this pop up again in the real import. state.trace("failed to parse: %r" % (fn,)) From 661013c3e922c17f2dcf90a96bcc74b95a681475 Mon Sep 17 00:00:00 2001 From: Tomer Keren Date: Mon, 15 Oct 2018 11:13:24 +0300 Subject: [PATCH 22/26] Add testdir examples to CONTRIBUTING guide Hopefully Closes: #4151 --- CONTRIBUTING.rst | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index c005c2fb254..eb36122b5a6 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -280,6 +280,37 @@ Here is a simple overview, with pytest-specific bits: base: features # if it's a feature +Writing Tests +---------------------------- + +Writing tests for plugins or for pytest itself is done using the `testdir fixture `_, + +For example: + +.. code-block:: python + + def test_true_assertion(testdir): + testdir.makepyfile( + """ + def test_foo(): + assert True + """ + ) + result = testdir.runpytest() + result.assert_outcomes(failed=0, passed=1) + + + def test_true_assertion(testdir): + testdir.makepyfile( + """ + def test_foo(): + assert False + """ + ) + result = testdir.runpytest() + result.assert_outcomes(failed=1, passed=0) + + Joining the Development Team ---------------------------- From 99d957bd3df2bd4fcb4a4603a2073d6fc448b1e6 Mon Sep 17 00:00:00 2001 From: Tomer Keren Date: Mon, 15 Oct 2018 11:36:31 +0300 Subject: [PATCH 23/26] Check off PR requirements --- AUTHORS | 1 + changelog/4151.doc.rst | 1 + 2 files changed, 2 insertions(+) create mode 100644 changelog/4151.doc.rst diff --git a/AUTHORS b/AUTHORS index 2be74441a27..463810bd9f6 100644 --- a/AUTHORS +++ b/AUTHORS @@ -209,6 +209,7 @@ Thomas Hisch Tim Strazny Tom Dalton Tom Viner +Tomer Keren Trevor Bekolay Tyler Goodlet Tzu-ping Chung diff --git a/changelog/4151.doc.rst b/changelog/4151.doc.rst new file mode 100644 index 00000000000..da561002a5d --- /dev/null +++ b/changelog/4151.doc.rst @@ -0,0 +1 @@ +Add tempir testing example to CONTRIBUTING.rst guide From f129ba617f8d481ba8469f2e039a574c8717b312 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 15 Oct 2018 08:00:16 -0300 Subject: [PATCH 24/26] Improve docs a bit --- CONTRIBUTING.rst | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index eb36122b5a6..c8ef03209ad 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -283,9 +283,9 @@ Here is a simple overview, with pytest-specific bits: Writing Tests ---------------------------- -Writing tests for plugins or for pytest itself is done using the `testdir fixture `_, +Writing tests for plugins or for pytest itself is often done using the `testdir fixture `_, as a "black-box" test. -For example: +For example, to ensure a simple test passes you can write: .. code-block:: python @@ -300,6 +300,11 @@ For example: result.assert_outcomes(failed=0, passed=1) +Alternatively, it is possible to make checks based on the actual output of the termal using +*glob-like* expressions: + +.. code-block:: python + def test_true_assertion(testdir): testdir.makepyfile( """ @@ -308,7 +313,12 @@ For example: """ ) result = testdir.runpytest() - result.assert_outcomes(failed=1, passed=0) + result.stdout.fnmatch_lines(["*assert False*, "*1 failed*"]) + +When choosing a file where to write a new test, take a look at the existing files and see if there's +one file which looks like a good fit. For example, a regression test about a bug in the ``--lf`` option +should go into ``test_cacheprovider.py``, given that this option is implemented in ``cacheprovider.py``. +If in doubt, go ahead and open a PR with your best guess and we can discuss this over the code. Joining the Development Team From ea25eb1ecc679cb1ef3a88aefe7d6bc30fa978c2 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 15 Oct 2018 08:15:40 -0300 Subject: [PATCH 25/26] Fix linting --- CONTRIBUTING.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index c8ef03209ad..d3202f7c835 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -313,8 +313,8 @@ Alternatively, it is possible to make checks based on the actual output of the t """ ) result = testdir.runpytest() - result.stdout.fnmatch_lines(["*assert False*, "*1 failed*"]) - + result.stdout.fnmatch_lines(["*assert False*", "*1 failed*"]) + When choosing a file where to write a new test, take a look at the existing files and see if there's one file which looks like a good fit. For example, a regression test about a bug in the ``--lf`` option should go into ``test_cacheprovider.py``, given that this option is implemented in ``cacheprovider.py``. From 9e867ce8642d6ed6e672c6aa770a034c62f5573e Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 15 Oct 2018 12:19:52 -0300 Subject: [PATCH 26/26] Use full link to changelog's README in PR template Fix #4156 --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 23a9f8c5683..7054f063d84 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,7 +3,7 @@ Thanks for submitting a PR, your contribution is really appreciated! Here's a quick checklist that should be present in PRs (you can delete this text from the final description, this is just a guideline): -- [ ] Create a new changelog file in the `changelog` folder, with a name like `..rst`. See [changelog/README.rst](/changelog/README.rst) for details. +- [ ] Create a new changelog file in the `changelog` folder, with a name like `..rst`. See [changelog/README.rst](https://github.com/pytest-dev/pytest/blob/master/changelog/README.rst) for details. - [ ] Target the `master` branch for bug fixes, documentation updates and trivial changes. - [ ] Target the `features` branch for new features and removals/deprecations. - [ ] Include documentation when adding new features.