Skip to content
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

Exit pytest on collection error (without executing tests) #1628

Merged
merged 1 commit into from
Jun 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Michael Birtwell
Michael Droettboom
Mike Lundy
Nicolas Delaby
Oleg Pidsadnyi
Omar Kohl
Pieter Mulder
Piotr Banaszkiewicz
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@
message to raise when no exception occurred.
Thanks `@palaviv`_ for the complete PR (`#1616`_).

* Fix `#1421`_: Exit tests if a collection error occurs and add
``--continue-on-collection-errors`` option to restore previous behaviour.
Thanks `@olegpidsadnyi`_ and `@omarkohl`_ for the complete PR (`#1628`_).

.. _@milliams: https://github.com/milliams
.. _@csaftoiu: https://github.com/csaftoiu
.. _@novas0x2a: https://github.com/novas0x2a
Expand All @@ -83,7 +87,9 @@
.. _@palaviv: https://github.com/palaviv
.. _@omarkohl: https://github.com/omarkohl
.. _@mikofski: https://github.com/mikofski
.. _@olegpidsadnyi: https://github.com/olegpidsadnyi

.. _#1421: https://github.com/pytest-dev/pytest/issues/1421
.. _#1426: https://github.com/pytest-dev/pytest/issues/1426
.. _#1428: https://github.com/pytest-dev/pytest/pull/1428
.. _#1444: https://github.com/pytest-dev/pytest/pull/1444
Expand All @@ -98,6 +104,7 @@
.. _#372: https://github.com/pytest-dev/pytest/issues/372
.. _#1544: https://github.com/pytest-dev/pytest/issues/1544
.. _#1616: https://github.com/pytest-dev/pytest/pull/1616
.. _#1628: https://github.com/pytest-dev/pytest/pull/1628


**Bug Fixes**
Expand Down
8 changes: 8 additions & 0 deletions _pytest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ def pytest_addoption(parser):
help="run pytest in strict mode, warnings become errors.")
group._addoption("-c", metavar="file", type=str, dest="inifilename",
help="load configuration from `file` instead of trying to locate one of the implicit configuration files.")
group._addoption("--continue-on-collection-errors", action="store_true",
default=False, dest="continue_on_collection_errors",
help="Force test execution even if collection errors occur.")

group = parser.getgroup("collect", "collection")
group.addoption('--collectonly', '--collect-only', action="store_true",
Expand Down Expand Up @@ -133,6 +136,11 @@ def pytest_collection(session):
return session.perform_collect()

def pytest_runtestloop(session):
if (session.testsfailed and
not session.config.option.continue_on_collection_errors):
raise session.Interrupted(
"%d errors during collection" % session.testsfailed)

if session.config.option.collectonly:
return True

Expand Down
10 changes: 6 additions & 4 deletions testing/acceptance_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def test_this():
"ImportError while importing test module*",
"'No module named *does_not_work*",
])
assert result.ret == 1
assert result.ret == 2

def test_not_collectable_arguments(self, testdir):
p1 = testdir.makepyfile("")
Expand Down Expand Up @@ -665,11 +665,13 @@ def test_with_failing_collection(self, testdir):
testdir.makepyfile(self.source)
testdir.makepyfile(test_collecterror="""xyz""")
result = testdir.runpytest("--durations=2", "-k test_1")
assert result.ret != 0
assert result.ret == 2
result.stdout.fnmatch_lines([
"*durations*",
"*call*test_1*",
"*Interrupted: 1 errors during collection*",
])
# Collection errors abort test execution, therefore no duration is
# output
assert "duration" not in result.stdout.str()

def test_with_not(self, testdir):
testdir.makepyfile(self.source)
Expand Down
111 changes: 111 additions & 0 deletions testing/test_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,3 +642,114 @@ def test___repr__():
""")
reprec = testdir.inline_run("-k repr")
reprec.assertoutcome(passed=1, failed=0)


COLLECTION_ERROR_PY_FILES = dict(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a tip: sometimes these type of metadata is better put into a session fixture. 😉

test_01_failure="""
def test_1():
assert False
""",
test_02_import_error="""
import asdfasdfasdf
def test_2():
assert True
""",
test_03_import_error="""
import asdfasdfasdf
def test_3():
assert True
""",
test_04_success="""
def test_4():
assert True
""",
)

def test_exit_on_collection_error(testdir):
"""Verify that all collection errors are collected and no tests executed"""
testdir.makepyfile(**COLLECTION_ERROR_PY_FILES)

res = testdir.runpytest()
assert res.ret == 2

res.stdout.fnmatch_lines([
"collected 2 items / 2 errors",
"*ERROR collecting test_02_import_error.py*",
"*No module named *asdfa*",
"*ERROR collecting test_03_import_error.py*",
"*No module named *asdfa*",
])


def test_exit_on_collection_with_maxfail_smaller_than_n_errors(testdir):
"""
Verify collection is aborted once maxfail errors are encountered ignoring
further modules which would cause more collection errors.
"""
testdir.makepyfile(**COLLECTION_ERROR_PY_FILES)

res = testdir.runpytest("--maxfail=1")
assert res.ret == 2

res.stdout.fnmatch_lines([
"*ERROR collecting test_02_import_error.py*",
"*No module named *asdfa*",
"*Interrupted: stopping after 1 failures*",
])

assert 'test_03' not in res.stdout.str()


def test_exit_on_collection_with_maxfail_bigger_than_n_errors(testdir):
"""
Verify the test run aborts due to collection errors even if maxfail count of
errors was not reached.
"""
testdir.makepyfile(**COLLECTION_ERROR_PY_FILES)

res = testdir.runpytest("--maxfail=4")
assert res.ret == 2

res.stdout.fnmatch_lines([
"collected 2 items / 2 errors",
"*ERROR collecting test_02_import_error.py*",
"*No module named *asdfa*",
"*ERROR collecting test_03_import_error.py*",
"*No module named *asdfa*",
])


def test_continue_on_collection_errors(testdir):
"""
Verify tests are executed even when collection errors occur when the
--continue-on-collection-errors flag is set
"""
testdir.makepyfile(**COLLECTION_ERROR_PY_FILES)

res = testdir.runpytest("--continue-on-collection-errors")
assert res.ret == 1

res.stdout.fnmatch_lines([
"collected 2 items / 2 errors",
"*1 failed, 1 passed, 2 error*",
])


def test_continue_on_collection_errors_maxfail(testdir):
"""
Verify tests are executed even when collection errors occur and that maxfail
is honoured (including the collection error count).
4 tests: 2 collection errors + 1 failure + 1 success
test_4 is never executed because the test run is with --maxfail=3 which
means it is interrupted after the 2 collection errors + 1 failure.
"""
testdir.makepyfile(**COLLECTION_ERROR_PY_FILES)

res = testdir.runpytest("--continue-on-collection-errors", "--maxfail=3")
assert res.ret == 2

res.stdout.fnmatch_lines([
"collected 2 items / 2 errors",
"*Interrupted: stopping after 3 failures*",
"*1 failed, 2 error*",
])
21 changes: 17 additions & 4 deletions testing/test_doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,20 @@ def test(self):
"*1 failed*",
])

def test_doctest_unex_importerror_only_txt(self, testdir):
testdir.maketxtfile("""
>>> import asdalsdkjaslkdjasd
>>>
""")
result = testdir.runpytest()
# doctest is never executed because of error during hello.py collection
result.stdout.fnmatch_lines([
"*>>> import asdals*",
"*UNEXPECTED*ImportError*",
"ImportError: No module named *asdal*",
])

def test_doctest_unex_importerror(self, testdir):
def test_doctest_unex_importerror_with_module(self, testdir):
testdir.tmpdir.join("hello.py").write(_pytest._code.Source("""
import asdalsdkjaslkdjasd
"""))
Expand All @@ -209,10 +221,11 @@ def test_doctest_unex_importerror(self, testdir):
>>>
""")
result = testdir.runpytest("--doctest-modules")
# doctest is never executed because of error during hello.py collection
result.stdout.fnmatch_lines([
"*>>> import hello",
"*UNEXPECTED*ImportError*",
"*import asdals*",
"*ERROR collecting hello.py*",
"*ImportError: No module named *asdals*",
"*Interrupted: 1 errors during collection*",
])

def test_doctestmodule(self, testdir):
Expand Down
2 changes: 1 addition & 1 deletion testing/test_resultlog.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,6 @@ def test_func():
pass
""")
result = testdir.runpytest("--resultlog=log")
assert result.ret == 1
assert result.ret == 2


2 changes: 1 addition & 1 deletion testing/test_terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def test_method(self):
def test_collectonly_error(self, testdir):
p = testdir.makepyfile("import Errlkjqweqwe")
result = testdir.runpytest("--collect-only", p)
assert result.ret == 1
assert result.ret == 2
result.stdout.fnmatch_lines(_pytest._code.Source("""
*ERROR*
*ImportError*
Expand Down