Raise CollectError if pytest.skip() is called during collection
pytest.skip() must not be used at module level because it can easily be misunderstood and used as a decorator instead of pytest.mark.skip, causing the whole module to be skipped instead of just the test being decorated. This is unexpected for users used to the @unittest.skip decorator and therefore it is best to bail out with a clean error when it happens. The pytest equivalent of @unittest.skip is @pytest.mark.skip . Adapt existing tests that were actually relying on this behaviour and add a test that explicitly test that collection fails. fix #607
This commit is contained in:
parent
f2bb3df310
commit
d81f23009b
|
@ -656,6 +656,12 @@ class Module(pytest.File, PyCollector):
|
||||||
"Make sure your test modules/packages have valid Python names."
|
"Make sure your test modules/packages have valid Python names."
|
||||||
% (self.fspath, exc or exc_class)
|
% (self.fspath, exc or exc_class)
|
||||||
)
|
)
|
||||||
|
except _pytest.runner.Skipped:
|
||||||
|
raise self.CollectError(
|
||||||
|
"Using @pytest.skip outside a test (e.g. as a test function "
|
||||||
|
"decorator) is not allowed. Use @pytest.mark.skip or "
|
||||||
|
"@pytest.mark.skipif instead."
|
||||||
|
)
|
||||||
#print "imported test module", mod
|
#print "imported test module", mod
|
||||||
self.config.pluginmanager.consider_module(mod)
|
self.config.pluginmanager.consider_module(mod)
|
||||||
return mod
|
return mod
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from xml.dom import minidom
|
from xml.dom import minidom
|
||||||
from _pytest.main import EXIT_NOTESTSCOLLECTED
|
|
||||||
import py
|
import py
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
@ -158,6 +157,47 @@ class TestPython:
|
||||||
snode = tnode.find_first_by_tag("skipped")
|
snode = tnode.find_first_by_tag("skipped")
|
||||||
snode.assert_attr(type="pytest.skip", message="hello23", )
|
snode.assert_attr(type="pytest.skip", message="hello23", )
|
||||||
|
|
||||||
|
def test_mark_skip_contains_name_reason(self, testdir):
|
||||||
|
testdir.makepyfile("""
|
||||||
|
import pytest
|
||||||
|
@pytest.mark.skip(reason="hello24")
|
||||||
|
def test_skip():
|
||||||
|
assert True
|
||||||
|
""")
|
||||||
|
result, dom = runandparse(testdir)
|
||||||
|
assert result.ret == 0
|
||||||
|
node = dom.find_first_by_tag("testsuite")
|
||||||
|
node.assert_attr(skips=1)
|
||||||
|
tnode = node.find_first_by_tag("testcase")
|
||||||
|
tnode.assert_attr(
|
||||||
|
file="test_mark_skip_contains_name_reason.py",
|
||||||
|
line="1",
|
||||||
|
classname="test_mark_skip_contains_name_reason",
|
||||||
|
name="test_skip")
|
||||||
|
snode = tnode.find_first_by_tag("skipped")
|
||||||
|
snode.assert_attr(type="pytest.skip", message="hello24", )
|
||||||
|
|
||||||
|
def test_mark_skipif_contains_name_reason(self, testdir):
|
||||||
|
testdir.makepyfile("""
|
||||||
|
import pytest
|
||||||
|
GLOBAL_CONDITION = True
|
||||||
|
@pytest.mark.skipif(GLOBAL_CONDITION, reason="hello25")
|
||||||
|
def test_skip():
|
||||||
|
assert True
|
||||||
|
""")
|
||||||
|
result, dom = runandparse(testdir)
|
||||||
|
assert result.ret == 0
|
||||||
|
node = dom.find_first_by_tag("testsuite")
|
||||||
|
node.assert_attr(skips=1)
|
||||||
|
tnode = node.find_first_by_tag("testcase")
|
||||||
|
tnode.assert_attr(
|
||||||
|
file="test_mark_skipif_contains_name_reason.py",
|
||||||
|
line="2",
|
||||||
|
classname="test_mark_skipif_contains_name_reason",
|
||||||
|
name="test_skip")
|
||||||
|
snode = tnode.find_first_by_tag("skipped")
|
||||||
|
snode.assert_attr(type="pytest.skip", message="hello25", )
|
||||||
|
|
||||||
def test_classname_instance(self, testdir):
|
def test_classname_instance(self, testdir):
|
||||||
testdir.makepyfile("""
|
testdir.makepyfile("""
|
||||||
class TestClass:
|
class TestClass:
|
||||||
|
@ -351,23 +391,6 @@ class TestPython:
|
||||||
fnode.assert_attr(message="collection failure")
|
fnode.assert_attr(message="collection failure")
|
||||||
assert "SyntaxError" in fnode.toxml()
|
assert "SyntaxError" in fnode.toxml()
|
||||||
|
|
||||||
def test_collect_skipped(self, testdir):
|
|
||||||
testdir.makepyfile("import pytest; pytest.skip('xyz')")
|
|
||||||
result, dom = runandparse(testdir)
|
|
||||||
assert result.ret == EXIT_NOTESTSCOLLECTED
|
|
||||||
node = dom.find_first_by_tag("testsuite")
|
|
||||||
node.assert_attr(skips=1, tests=1)
|
|
||||||
tnode = node.find_first_by_tag("testcase")
|
|
||||||
tnode.assert_attr(
|
|
||||||
file="test_collect_skipped.py",
|
|
||||||
name="test_collect_skipped")
|
|
||||||
|
|
||||||
# pytest doesn't give us a line here.
|
|
||||||
assert tnode["line"] is None
|
|
||||||
|
|
||||||
fnode = tnode.find_first_by_tag("skipped")
|
|
||||||
fnode.assert_attr(message="collection skipped")
|
|
||||||
|
|
||||||
def test_unicode(self, testdir):
|
def test_unicode(self, testdir):
|
||||||
value = 'hx\xc4\x85\xc4\x87\n'
|
value = 'hx\xc4\x85\xc4\x87\n'
|
||||||
testdir.makepyfile("""
|
testdir.makepyfile("""
|
||||||
|
|
|
@ -83,19 +83,10 @@ class TestWithFunctionIntegration:
|
||||||
|
|
||||||
def test_collection_report(self, testdir):
|
def test_collection_report(self, testdir):
|
||||||
ok = testdir.makepyfile(test_collection_ok="")
|
ok = testdir.makepyfile(test_collection_ok="")
|
||||||
skip = testdir.makepyfile(test_collection_skip=
|
|
||||||
"import pytest ; pytest.skip('hello')")
|
|
||||||
fail = testdir.makepyfile(test_collection_fail="XXX")
|
fail = testdir.makepyfile(test_collection_fail="XXX")
|
||||||
lines = self.getresultlog(testdir, ok)
|
lines = self.getresultlog(testdir, ok)
|
||||||
assert not lines
|
assert not lines
|
||||||
|
|
||||||
lines = self.getresultlog(testdir, skip)
|
|
||||||
assert len(lines) == 2
|
|
||||||
assert lines[0].startswith("S ")
|
|
||||||
assert lines[0].endswith("test_collection_skip.py")
|
|
||||||
assert lines[1].startswith(" ")
|
|
||||||
assert lines[1].endswith("test_collection_skip.py:1: Skipped: hello")
|
|
||||||
|
|
||||||
lines = self.getresultlog(testdir, fail)
|
lines = self.getresultlog(testdir, fail)
|
||||||
assert lines
|
assert lines
|
||||||
assert lines[0].startswith("F ")
|
assert lines[0].startswith("F ")
|
||||||
|
|
|
@ -365,18 +365,6 @@ class TestSessionReports:
|
||||||
assert res[0].name == "test_func1"
|
assert res[0].name == "test_func1"
|
||||||
assert res[1].name == "TestClass"
|
assert res[1].name == "TestClass"
|
||||||
|
|
||||||
def test_skip_at_module_scope(self, testdir):
|
|
||||||
col = testdir.getmodulecol("""
|
|
||||||
import pytest
|
|
||||||
pytest.skip("hello")
|
|
||||||
def test_func():
|
|
||||||
pass
|
|
||||||
""")
|
|
||||||
rep = main.collect_one_node(col)
|
|
||||||
assert not rep.failed
|
|
||||||
assert not rep.passed
|
|
||||||
assert rep.skipped
|
|
||||||
|
|
||||||
|
|
||||||
reporttypes = [
|
reporttypes = [
|
||||||
runner.BaseReport,
|
runner.BaseReport,
|
||||||
|
|
|
@ -174,10 +174,6 @@ class TestNewSession(SessionTests):
|
||||||
class TestY(TestX):
|
class TestY(TestX):
|
||||||
pass
|
pass
|
||||||
""",
|
""",
|
||||||
test_two="""
|
|
||||||
import pytest
|
|
||||||
pytest.skip('xxx')
|
|
||||||
""",
|
|
||||||
test_three="xxxdsadsadsadsa",
|
test_three="xxxdsadsadsadsa",
|
||||||
__init__=""
|
__init__=""
|
||||||
)
|
)
|
||||||
|
@ -189,11 +185,9 @@ class TestNewSession(SessionTests):
|
||||||
started = reprec.getcalls("pytest_collectstart")
|
started = reprec.getcalls("pytest_collectstart")
|
||||||
finished = reprec.getreports("pytest_collectreport")
|
finished = reprec.getreports("pytest_collectreport")
|
||||||
assert len(started) == len(finished)
|
assert len(started) == len(finished)
|
||||||
assert len(started) == 8 # XXX extra TopCollector
|
assert len(started) == 7 # XXX extra TopCollector
|
||||||
colfail = [x for x in finished if x.failed]
|
colfail = [x for x in finished if x.failed]
|
||||||
colskipped = [x for x in finished if x.skipped]
|
|
||||||
assert len(colfail) == 1
|
assert len(colfail) == 1
|
||||||
assert len(colskipped) == 1
|
|
||||||
|
|
||||||
def test_minus_x_import_error(self, testdir):
|
def test_minus_x_import_error(self, testdir):
|
||||||
testdir.makepyfile(__init__="")
|
testdir.makepyfile(__init__="")
|
||||||
|
|
|
@ -682,10 +682,6 @@ def test_skipped_reasons_functional(testdir):
|
||||||
def test_method(self):
|
def test_method(self):
|
||||||
doskip()
|
doskip()
|
||||||
""",
|
""",
|
||||||
test_two = """
|
|
||||||
from conftest import doskip
|
|
||||||
doskip()
|
|
||||||
""",
|
|
||||||
conftest = """
|
conftest = """
|
||||||
import pytest
|
import pytest
|
||||||
def doskip():
|
def doskip():
|
||||||
|
@ -694,7 +690,7 @@ def test_skipped_reasons_functional(testdir):
|
||||||
)
|
)
|
||||||
result = testdir.runpytest('--report=skipped')
|
result = testdir.runpytest('--report=skipped')
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*SKIP*3*conftest.py:3: test",
|
"*SKIP*2*conftest.py:3: test",
|
||||||
])
|
])
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
||||||
|
@ -941,3 +937,19 @@ def test_xfail_item(testdir):
|
||||||
assert not failed
|
assert not failed
|
||||||
xfailed = [r for r in skipped if hasattr(r, 'wasxfail')]
|
xfailed = [r for r in skipped if hasattr(r, 'wasxfail')]
|
||||||
assert xfailed
|
assert xfailed
|
||||||
|
|
||||||
|
|
||||||
|
def test_module_level_skip_error(testdir):
|
||||||
|
"""
|
||||||
|
Verify that using pytest.skip at module level causes a collection error
|
||||||
|
"""
|
||||||
|
testdir.makepyfile("""
|
||||||
|
import pytest
|
||||||
|
@pytest.skip
|
||||||
|
def test_func():
|
||||||
|
assert True
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
"*Using @pytest.skip outside a test * is not allowed*"
|
||||||
|
)
|
||||||
|
|
|
@ -223,8 +223,7 @@ class TestCollectonly:
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest("--collect-only", "-rs")
|
result = testdir.runpytest("--collect-only", "-rs")
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"SKIP*hello*",
|
"*ERROR collecting*",
|
||||||
"*1 skip*",
|
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_collectonly_failed_module(self, testdir):
|
def test_collectonly_failed_module(self, testdir):
|
||||||
|
|
Loading…
Reference in New Issue