Skip to content

Commit 4d49b95

Browse files
fix(pytest): properly propagate failures and skipped tests when used with unittest [backport 1.14] (#6209)
Backport ecaeac8 from #6204 to 1.14. Pytest: Propagate properly failures and skipped information when used with unittest.TestCase ## Checklist - [x] Change(s) are motivated and described in the PR description. - [x] Testing strategy is described if automated tests are not included in the PR. - [x] Risk is outlined (performance impact, potential for breakage, maintainability, etc). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) are followed. If no release note is required, add label `changelog/no-changelog`. - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)). - [x] Backport labels are set (if [applicable](../docs/contributing.rst#release-branch-maintenance)) ## Reviewer Checklist - [x] Title is accurate. - [x] No unnecessary changes are introduced. - [x] Description motivates each change. - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes unless absolutely necessary. - [x] Testing strategy adequately addresses listed risk(s). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] Release note makes sense to a user of the library. - [x] Reviewer has explicitly acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment. - [x] Backport labels are set in a manner that is consistent with the [release branch maintenance policy](../docs/contributing.rst#release-branch-maintenance) Co-authored-by: Federico Mon <[email protected]>
1 parent cb35259 commit 4d49b95

File tree

4 files changed

+101
-0
lines changed

4 files changed

+101
-0
lines changed

ddtrace/contrib/pytest/plugin.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ def _store_span(item, span):
6767

6868
def _mark_failed(item):
6969
"""Store test failed status at `pytest.Item` instance."""
70+
if item.parent:
71+
_mark_failed(item.parent)
7072
setattr(item, "_failed", True)
7173

7274

@@ -77,6 +79,8 @@ def _check_failed(item):
7779

7880
def _mark_not_skipped(item):
7981
"""Mark test suite/module/session `pytest.Item` as not skipped."""
82+
if item.parent:
83+
_mark_not_skipped(item.parent)
8084
setattr(item, "_fully_skipped", False)
8185

8286

docs/spelling_wordlist.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ submodule
199199
submodules
200200
substring
201201
testagent
202+
TestCase
202203
timestamp
203204
tokenizer
204205
tracestate
@@ -207,6 +208,7 @@ uWSGI
207208
unbuffered
208209
unicode
209210
uninstrumented
211+
unittest
210212
unix
211213
unpatch
212214
unpatched
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
fixes:
3+
- |
4+
pytest: This fix resolves an issue where failures and non-skipped tests were not propagated properly when
5+
unittest.TestCase classes were used.

tests/contrib/pytest/test_pytest.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,96 @@ def test_ok():
10671067
assert test_suite_span.get_tag("test.status") == "pass"
10681068
assert test_session_span.get_tag("test.status") == "pass"
10691069

1070+
def test_pytest_some_skipped_tests_does_not_propagate_in_testcase(self):
1071+
"""Test that if not all tests are skipped, that status does not propagate upwards."""
1072+
py_file = self.testdir.makepyfile(
1073+
"""
1074+
import unittest
1075+
import pytest
1076+
1077+
class MyTest(unittest.TestCase):
1078+
1079+
@pytest.mark.skip(reason="Because")
1080+
def test_not_ok_but_skipped(self):
1081+
assert 0
1082+
1083+
def test_ok(self):
1084+
assert True
1085+
"""
1086+
)
1087+
file_name = os.path.basename(py_file.strpath)
1088+
rec = self.inline_run("--ddtrace", file_name)
1089+
rec.assertoutcome(skipped=1, passed=1)
1090+
spans = self.pop_spans()
1091+
test_span_skipped = spans[0]
1092+
test_span_ok = spans[1]
1093+
test_suite_span = spans[3]
1094+
test_session_span = spans[2]
1095+
assert test_span_skipped.get_tag("test.status") == "skip"
1096+
assert test_span_ok.get_tag("test.status") == "pass"
1097+
assert test_suite_span.get_tag("test.status") == "pass"
1098+
assert test_session_span.get_tag("test.status") == "pass"
1099+
1100+
def test_pytest_all_skipped_tests_does_propagate_in_testcase(self):
1101+
"""Test that if all tests are skipped, that status is propagated upwards."""
1102+
py_file = self.testdir.makepyfile(
1103+
"""
1104+
import unittest
1105+
import pytest
1106+
1107+
class MyTest(unittest.TestCase):
1108+
1109+
@pytest.mark.skip(reason="Because")
1110+
def test_not_ok_but_skipped(self):
1111+
assert 0
1112+
1113+
@pytest.mark.skip(reason="Because")
1114+
def test_ok_but_skipped(self):
1115+
assert True
1116+
"""
1117+
)
1118+
file_name = os.path.basename(py_file.strpath)
1119+
rec = self.inline_run("--ddtrace", file_name)
1120+
rec.assertoutcome(skipped=2, passed=0)
1121+
spans = self.pop_spans()
1122+
test_span_skipped = spans[0]
1123+
test_span_ok = spans[1]
1124+
test_suite_span = spans[3]
1125+
test_session_span = spans[2]
1126+
assert test_span_skipped.get_tag("test.status") == "skip"
1127+
assert test_span_ok.get_tag("test.status") == "skip"
1128+
assert test_suite_span.get_tag("test.status") == "skip"
1129+
assert test_session_span.get_tag("test.status") == "skip"
1130+
1131+
def test_pytest_failed_tests_propagate_in_testcase(self):
1132+
"""Test that if any test fails, that status is propagated upwards."""
1133+
py_file = self.testdir.makepyfile(
1134+
"""
1135+
import unittest
1136+
import pytest
1137+
1138+
class MyTest(unittest.TestCase):
1139+
1140+
def test_not_ok(self):
1141+
assert 0
1142+
1143+
def test_ok(self):
1144+
assert True
1145+
"""
1146+
)
1147+
file_name = os.path.basename(py_file.strpath)
1148+
rec = self.inline_run("--ddtrace", file_name)
1149+
rec.assertoutcome(failed=1, passed=1)
1150+
spans = self.pop_spans()
1151+
test_span_skipped = spans[0]
1152+
test_span_ok = spans[1]
1153+
test_suite_span = spans[3]
1154+
test_session_span = spans[2]
1155+
assert test_span_skipped.get_tag("test.status") == "fail"
1156+
assert test_span_ok.get_tag("test.status") == "pass"
1157+
assert test_suite_span.get_tag("test.status") == "fail"
1158+
assert test_session_span.get_tag("test.status") == "fail"
1159+
10701160
def test_pytest_module(self):
10711161
"""Test that running pytest on a test package will generate a test module span."""
10721162
package_a_dir = self.testdir.mkpydir("test_package_a")

0 commit comments

Comments
 (0)