-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Stepwise #4147
Merged
RonnyPfannschmidt
merged 17 commits into
pytest-dev:features
from
davidszotten:stepwise
Oct 27, 2018
Merged
Stepwise #4147
Changes from 15 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
6610551
Restructured project.
nip3o 1d23bef
Use a single node ID rather than a set for failed tests.
nip3o 33f1ff4
Use result.stderr in tests since result.errlines has changed behaviour.
nip3o bd94954
pytest 2.7 compatibility.
nip3o d9c428c
add compat for pytest 3.7 and tox config for (some of) the versions i…
davidszotten c56d7ac
move files into the pytest file structure
davidszotten 63c01d1
update for builtin plugin
davidszotten fd66f69
draft doc
davidszotten 8c059db
draft changelog
davidszotten 126bb07
authors
davidszotten 4f652c9
we have a pr number now
davidszotten e773c8c
linting
davidszotten 8187c14
now pinned to pytest version
davidszotten d67d189
grammar
davidszotten c25310d
fix cacheprovider test
davidszotten e478f66
cache is set by the cacheprovider
davidszotten f947cb2
Merge remote-tracking branch 'upstream/features' into davidszotten/st…
nicoddemus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Add ``-sw``, ``--stepwise`` as an alternative to ``--lf -x`` for stopping at the first failure, but starting the next test invocation from that test. See `the documentation <https://docs.pytest.org/en/latest/cache.html#stepwise>`_ for more info. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
from _pytest.cacheprovider import Cache | ||
import pytest | ||
|
||
|
||
def pytest_addoption(parser): | ||
group = parser.getgroup("general") | ||
group.addoption( | ||
"--sw", | ||
"--stepwise", | ||
action="store_true", | ||
dest="stepwise", | ||
help="exit on test fail and continue from last failing test next time", | ||
) | ||
group.addoption( | ||
"--stepwise-skip", | ||
action="store_true", | ||
dest="stepwise_skip", | ||
help="ignore the first failing test but stop on the next failing test", | ||
) | ||
|
||
|
||
@pytest.hookimpl(tryfirst=True) | ||
def pytest_configure(config): | ||
config.cache = Cache.for_config(config) | ||
config.pluginmanager.register(StepwisePlugin(config), "stepwiseplugin") | ||
|
||
|
||
class StepwisePlugin: | ||
def __init__(self, config): | ||
self.config = config | ||
self.active = config.getvalue("stepwise") | ||
self.session = None | ||
|
||
if self.active: | ||
self.lastfailed = config.cache.get("cache/stepwise", None) | ||
self.skip = config.getvalue("stepwise_skip") | ||
|
||
def pytest_sessionstart(self, session): | ||
self.session = session | ||
|
||
def pytest_collection_modifyitems(self, session, config, items): | ||
if not self.active or not self.lastfailed: | ||
return | ||
|
||
already_passed = [] | ||
found = False | ||
|
||
# Make a list of all tests that have been run before the last failing one. | ||
for item in items: | ||
if item.nodeid == self.lastfailed: | ||
found = True | ||
break | ||
else: | ||
already_passed.append(item) | ||
|
||
# If the previously failed test was not found among the test items, | ||
# do not skip any tests. | ||
if not found: | ||
already_passed = [] | ||
|
||
for item in already_passed: | ||
items.remove(item) | ||
|
||
config.hook.pytest_deselected(items=already_passed) | ||
|
||
def pytest_collectreport(self, report): | ||
if self.active and report.failed: | ||
self.session.shouldstop = ( | ||
"Error when collecting test, stopping test execution." | ||
) | ||
|
||
def pytest_runtest_logreport(self, report): | ||
# Skip this hook if plugin is not active or the test is xfailed. | ||
if not self.active or "xfail" in report.keywords: | ||
return | ||
|
||
if report.failed: | ||
if self.skip: | ||
# Remove test from the failed ones (if it exists) and unset the skip option | ||
# to make sure the following tests will not be skipped. | ||
if report.nodeid == self.lastfailed: | ||
self.lastfailed = None | ||
|
||
self.skip = False | ||
else: | ||
# Mark test as the last failing and interrupt the test session. | ||
self.lastfailed = report.nodeid | ||
self.session.shouldstop = ( | ||
"Test failed, continuing from this test next run." | ||
) | ||
|
||
else: | ||
# If the test was actually run and did pass. | ||
if report.when == "call": | ||
# Remove test from the failed ones, if exists. | ||
if report.nodeid == self.lastfailed: | ||
self.lastfailed = None | ||
|
||
def pytest_sessionfinish(self, session): | ||
if self.active: | ||
self.config.cache.set("cache/stepwise", self.lastfailed) | ||
else: | ||
# Clear the list of failing tests if the plugin is not active. | ||
self.config.cache.set("cache/stepwise", []) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
import pytest | ||
|
||
|
||
@pytest.fixture | ||
def stepwise_testdir(testdir): | ||
# Rather than having to modify our testfile between tests, we introduce | ||
# a flag for wether or not the second test should fail. | ||
testdir.makeconftest( | ||
""" | ||
def pytest_addoption(parser): | ||
group = parser.getgroup('general') | ||
group.addoption('--fail', action='store_true', dest='fail') | ||
group.addoption('--fail-last', action='store_true', dest='fail_last') | ||
""" | ||
) | ||
|
||
# Create a simple test suite. | ||
testdir.makepyfile( | ||
test_a=""" | ||
def test_success_before_fail(): | ||
assert 1 | ||
|
||
def test_fail_on_flag(request): | ||
assert not request.config.getvalue('fail') | ||
|
||
def test_success_after_fail(): | ||
assert 1 | ||
|
||
def test_fail_last_on_flag(request): | ||
assert not request.config.getvalue('fail_last') | ||
|
||
def test_success_after_last_fail(): | ||
assert 1 | ||
""" | ||
) | ||
|
||
testdir.makepyfile( | ||
test_b=""" | ||
def test_success(): | ||
assert 1 | ||
""" | ||
) | ||
|
||
return testdir | ||
|
||
|
||
@pytest.fixture | ||
def error_testdir(testdir): | ||
testdir.makepyfile( | ||
test_a=""" | ||
def test_error(nonexisting_fixture): | ||
assert 1 | ||
|
||
def test_success_after_fail(): | ||
assert 1 | ||
""" | ||
) | ||
|
||
return testdir | ||
|
||
|
||
@pytest.fixture | ||
def broken_testdir(testdir): | ||
testdir.makepyfile( | ||
working_testfile="def test_proper(): assert 1", broken_testfile="foobar" | ||
) | ||
return testdir | ||
|
||
|
||
def test_run_without_stepwise(stepwise_testdir): | ||
result = stepwise_testdir.runpytest("-v", "--strict", "--fail") | ||
|
||
result.stdout.fnmatch_lines(["*test_success_before_fail PASSED*"]) | ||
result.stdout.fnmatch_lines(["*test_fail_on_flag FAILED*"]) | ||
result.stdout.fnmatch_lines(["*test_success_after_fail PASSED*"]) | ||
|
||
|
||
def test_fail_and_continue_with_stepwise(stepwise_testdir): | ||
# Run the tests with a failing second test. | ||
result = stepwise_testdir.runpytest("-v", "--strict", "--stepwise", "--fail") | ||
assert not result.stderr.str() | ||
|
||
stdout = result.stdout.str() | ||
# Make sure we stop after first failing test. | ||
assert "test_success_before_fail PASSED" in stdout | ||
assert "test_fail_on_flag FAILED" in stdout | ||
assert "test_success_after_fail" not in stdout | ||
|
||
# "Fix" the test that failed in the last run and run it again. | ||
result = stepwise_testdir.runpytest("-v", "--strict", "--stepwise") | ||
assert not result.stderr.str() | ||
|
||
stdout = result.stdout.str() | ||
# Make sure the latest failing test runs and then continues. | ||
assert "test_success_before_fail" not in stdout | ||
assert "test_fail_on_flag PASSED" in stdout | ||
assert "test_success_after_fail PASSED" in stdout | ||
|
||
|
||
def test_run_with_skip_option(stepwise_testdir): | ||
result = stepwise_testdir.runpytest( | ||
"-v", "--strict", "--stepwise", "--stepwise-skip", "--fail", "--fail-last" | ||
) | ||
assert not result.stderr.str() | ||
|
||
stdout = result.stdout.str() | ||
# Make sure first fail is ignore and second fail stops the test run. | ||
assert "test_fail_on_flag FAILED" in stdout | ||
assert "test_success_after_fail PASSED" in stdout | ||
assert "test_fail_last_on_flag FAILED" in stdout | ||
assert "test_success_after_last_fail" not in stdout | ||
|
||
|
||
def test_fail_on_errors(error_testdir): | ||
result = error_testdir.runpytest("-v", "--strict", "--stepwise") | ||
|
||
assert not result.stderr.str() | ||
stdout = result.stdout.str() | ||
|
||
assert "test_error ERROR" in stdout | ||
assert "test_success_after_fail" not in stdout | ||
|
||
|
||
def test_change_testfile(stepwise_testdir): | ||
result = stepwise_testdir.runpytest( | ||
"-v", "--strict", "--stepwise", "--fail", "test_a.py" | ||
) | ||
assert not result.stderr.str() | ||
|
||
stdout = result.stdout.str() | ||
assert "test_fail_on_flag FAILED" in stdout | ||
|
||
# Make sure the second test run starts from the beginning, since the | ||
# test to continue from does not exist in testfile_b. | ||
result = stepwise_testdir.runpytest("-v", "--strict", "--stepwise", "test_b.py") | ||
assert not result.stderr.str() | ||
|
||
stdout = result.stdout.str() | ||
assert "test_success PASSED" in stdout | ||
|
||
|
||
def test_stop_on_collection_errors(broken_testdir): | ||
result = broken_testdir.runpytest( | ||
"-v", "--strict", "--stepwise", "working_testfile.py", "broken_testfile.py" | ||
) | ||
|
||
stdout = result.stdout.str() | ||
assert "errors during collection" in stdout |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to remove this line because
cacheprovider
already creates the cache for us:pytest/src/_pytest/cacheprovider.py
Lines 294 to 297 in f7285b6
We should also remove the
tryfirst=True
option from the hook implementation socacheprovider
executes first.