pytest-tap 3.3__tar.gz → 3.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ env/
12
+ build/
13
+ develop-eggs/
14
+ dist/
15
+ downloads/
16
+ eggs/
17
+ .eggs/
18
+ lib/
19
+ lib64/
20
+ parts/
21
+ sdist/
22
+ var/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+
27
+ # PyInstaller
28
+ # Usually these files are written by a python script from a template
29
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
30
+ *.manifest
31
+ *.spec
32
+
33
+ # Installer logs
34
+ pip-log.txt
35
+ pip-delete-this-directory.txt
36
+
37
+ # Unit test / coverage reports
38
+ htmlcov/
39
+ .tox/
40
+ .coverage
41
+ .coverage.*
42
+ .cache
43
+ nosetests.xml
44
+ coverage.xml
45
+ *,cover
46
+ .hypothesis/
47
+ .pytest_cache
48
+
49
+ # Translations
50
+ *.mo
51
+
52
+ # Django stuff:
53
+ *.log
54
+
55
+ # Sphinx documentation
56
+ docs/_build/
57
+
58
+ # PyBuilder
59
+ target/
60
+
61
+ #Ipython Notebook
62
+ .ipynb_checkpoints
63
+
64
+ # Vim
65
+ *.sw*
66
+
67
+ # TAP
68
+ *.tap
69
+
70
+ # Virtual environments
71
+ venv/
@@ -3,6 +3,8 @@ pytest-tap was originally created by Matt Layman.
3
3
  Contributors
4
4
  ------------
5
5
 
6
+ * Allison Karlitskaya
7
+ * Cody D'Ambrosio
6
8
  * Dan Dofter
7
9
  * Erik Cederstrand
8
10
  * Frédéric Mangano-Tarumi
@@ -1,40 +1,63 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pytest-tap
3
- Version: 3.3
3
+ Version: 3.5
4
4
  Summary: Test Anything Protocol (TAP) reporting plugin for pytest
5
- Home-page: https://github.com/python-tap/pytest-tap
6
- Author: Matt Layman
7
- Author-email: matthewlayman@gmail.com
5
+ Author-email: Matt Layman <matthewlayman@gmail.com>
8
6
  License: BSD
9
- Keywords: TAP,unittest,pytest
10
- Platform: any
7
+ License-File: AUTHORS
8
+ License-File: LICENSE
9
+ Keywords: TAP,pytest,unittest
11
10
  Classifier: Development Status :: 5 - Production/Stable
12
11
  Classifier: Framework :: Pytest
13
12
  Classifier: Intended Audience :: Developers
14
13
  Classifier: License :: OSI Approved :: BSD License
15
14
  Classifier: Operating System :: OS Independent
16
- Classifier: Programming Language :: Python :: 3.6
17
- Classifier: Programming Language :: Python :: 3.7
18
- Classifier: Programming Language :: Python :: 3.8
19
15
  Classifier: Programming Language :: Python :: 3.9
20
16
  Classifier: Programming Language :: Python :: 3.10
21
- Classifier: Programming Language :: Python :: Implementation :: PyPy
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
22
20
  Classifier: Topic :: Software Development :: Testing
23
- License-File: LICENSE
24
-
21
+ Requires-Python: >=3.9
22
+ Requires-Dist: pytest>=3.0
23
+ Requires-Dist: tap-py<4.0,>=3.2
24
+ Description-Content-Type: text/x-rst
25
25
 
26
26
  pytest-tap is a reporting plugin for pytest that outputs
27
27
  `Test Anything Protocol (TAP) <http://testanything.org/>`_ data.
28
28
  TAP is a line based test protocol for recording test data in a standard way.
29
29
 
30
- Follow development on `GitHub <https://github.com/python-tap/pytest-tap>`_.
31
- Developer documentation is on
32
- `Read the Docs <https://tappy.readthedocs.io/>`_.
30
+ Use ``pytest --tap`` after installing to get started.
33
31
 
32
+ Follow `GitHub <https://github.com/python-tap/pytest-tap>`_
33
+ for more information or to follow this plugin's development.
34
+ Additional developer documentation about Python and TAP is on
35
+ `Read the Docs <https://tappy.readthedocs.io/>`_.
34
36
 
35
37
  Releases
36
38
  ========
37
39
 
40
+ Version 3.5, January 30, 2025
41
+ -----------------------------
42
+
43
+ * Diagnostics now output logs, stdout, stderr for failed tests.
44
+ Use the standard ``--show-capture`` flag to control the output.
45
+ * Diagnostics can display for passing tests using the
46
+ ``--tap-log-passing-tests`` flag.
47
+ * Add support for Python 3.12.
48
+ * Add support for Python 3.13.
49
+ * Drop support for Python 3.8 (it is end-of-life).
50
+
51
+ Version 3.4, July 15, 2023
52
+ --------------------------
53
+
54
+ * Deprecate ``--tap-stream`` in favor of ``--tap`` for streaming mode.
55
+ * When using xdist, report the plan at the beginning of execution.
56
+ * Add support for Python 3.11.
57
+ * Drop support for Python 3.7 (it is end-of-life).
58
+ * Drop support for Python 3.6 (it is end-of-life).
59
+ * Drop support for PyPy.
60
+
38
61
  Version 3.3, October 27, 2021
39
62
  -----------------------------
40
63
 
@@ -113,5 +136,3 @@ Version 1.9, Released June 11, 2016
113
136
 
114
137
  * Initial release as stand-alone plugin.
115
138
  The version number aligns with tappy.
116
-
117
-
@@ -1,40 +1,38 @@
1
- Metadata-Version: 2.1
2
- Name: pytest-tap
3
- Version: 3.3
4
- Summary: Test Anything Protocol (TAP) reporting plugin for pytest
5
- Home-page: https://github.com/python-tap/pytest-tap
6
- Author: Matt Layman
7
- Author-email: matthewlayman@gmail.com
8
- License: BSD
9
- Keywords: TAP,unittest,pytest
10
- Platform: any
11
- Classifier: Development Status :: 5 - Production/Stable
12
- Classifier: Framework :: Pytest
13
- Classifier: Intended Audience :: Developers
14
- Classifier: License :: OSI Approved :: BSD License
15
- Classifier: Operating System :: OS Independent
16
- Classifier: Programming Language :: Python :: 3.6
17
- Classifier: Programming Language :: Python :: 3.7
18
- Classifier: Programming Language :: Python :: 3.8
19
- Classifier: Programming Language :: Python :: 3.9
20
- Classifier: Programming Language :: Python :: 3.10
21
- Classifier: Programming Language :: Python :: Implementation :: PyPy
22
- Classifier: Topic :: Software Development :: Testing
23
- License-File: LICENSE
24
-
25
-
26
1
  pytest-tap is a reporting plugin for pytest that outputs
27
2
  `Test Anything Protocol (TAP) <http://testanything.org/>`_ data.
28
3
  TAP is a line based test protocol for recording test data in a standard way.
29
4
 
30
- Follow development on `GitHub <https://github.com/python-tap/pytest-tap>`_.
31
- Developer documentation is on
32
- `Read the Docs <https://tappy.readthedocs.io/>`_.
5
+ Use ``pytest --tap`` after installing to get started.
33
6
 
7
+ Follow `GitHub <https://github.com/python-tap/pytest-tap>`_
8
+ for more information or to follow this plugin's development.
9
+ Additional developer documentation about Python and TAP is on
10
+ `Read the Docs <https://tappy.readthedocs.io/>`_.
34
11
 
35
12
  Releases
36
13
  ========
37
14
 
15
+ Version 3.5, January 30, 2025
16
+ -----------------------------
17
+
18
+ * Diagnostics now output logs, stdout, stderr for failed tests.
19
+ Use the standard ``--show-capture`` flag to control the output.
20
+ * Diagnostics can display for passing tests using the
21
+ ``--tap-log-passing-tests`` flag.
22
+ * Add support for Python 3.12.
23
+ * Add support for Python 3.13.
24
+ * Drop support for Python 3.8 (it is end-of-life).
25
+
26
+ Version 3.4, July 15, 2023
27
+ --------------------------
28
+
29
+ * Deprecate ``--tap-stream`` in favor of ``--tap`` for streaming mode.
30
+ * When using xdist, report the plan at the beginning of execution.
31
+ * Add support for Python 3.11.
32
+ * Drop support for Python 3.7 (it is end-of-life).
33
+ * Drop support for Python 3.6 (it is end-of-life).
34
+ * Drop support for PyPy.
35
+
38
36
  Version 3.3, October 27, 2021
39
37
  -----------------------------
40
38
 
@@ -113,5 +111,3 @@ Version 1.9, Released June 11, 2016
113
111
 
114
112
  * Initial release as stand-alone plugin.
115
113
  The version number aligns with tappy.
116
-
117
-
@@ -0,0 +1,83 @@
1
+ [project]
2
+ name = "pytest-tap"
3
+ version = "3.5"
4
+ description = "Test Anything Protocol (TAP) reporting plugin for pytest"
5
+ readme = "docs/releases.rst"
6
+ authors = [
7
+ { name = "Matt Layman", email = "matthewlayman@gmail.com" }
8
+ ]
9
+ license = { text = "BSD" }
10
+ homepage = "https://github.com/python-tap/pytest-tap"
11
+ dependencies = [
12
+ "pytest>=3.0",
13
+ "tap.py>=3.2,<4.0"
14
+ ]
15
+ requires-python = ">=3.9"
16
+ keywords = ["TAP", "unittest", "pytest"]
17
+ classifiers = [
18
+ "Development Status :: 5 - Production/Stable",
19
+ "Framework :: Pytest",
20
+ "Intended Audience :: Developers",
21
+ "License :: OSI Approved :: BSD License",
22
+ "Operating System :: OS Independent",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Topic :: Software Development :: Testing"
29
+ ]
30
+
31
+ [project.entry-points.pytest11]
32
+ tap = "pytest_tap.plugin"
33
+
34
+ [dependency-groups]
35
+ dev = [
36
+ "pytest-tap",
37
+ "tox>=4.24.1",
38
+ ]
39
+
40
+ [build-system]
41
+ requires = ["hatchling"]
42
+ build-backend = "hatchling.build"
43
+
44
+ [tool.hatch.build.targets.sdist]
45
+ include = [
46
+ "/src",
47
+ "/docs",
48
+ "/tests",
49
+ ]
50
+ sources = ["src"]
51
+
52
+ [tool.hatch.build.targets.wheel]
53
+ sources = ["src"]
54
+ packages = ["pytest_tap"]
55
+
56
+ [tool.pytest.ini_options]
57
+ pythonpath = [".", "src"]
58
+
59
+ [tool.uv.sources]
60
+ pytest-tap = { workspace = true }
61
+
62
+ [tool.ruff.lint]
63
+ select = [
64
+ # pycodestyle
65
+ "E",
66
+ "W",
67
+ # Pyflakes
68
+ "F",
69
+ # pyupgrade
70
+ "UP",
71
+ # flake8-bandit
72
+ "S",
73
+ # flake8-bugbear
74
+ "B",
75
+ # flake8-simplify
76
+ "SIM",
77
+ # isort
78
+ "I",
79
+ ]
80
+ ignore = [
81
+ # bandit: Use of `assert` detected
82
+ "S101",
83
+ ]
@@ -0,0 +1 @@
1
+ __version__ = "3.5"
@@ -0,0 +1,203 @@
1
+ import argparse
2
+ import sys
3
+
4
+ import pytest
5
+ from tap.formatter import format_as_diagnostics
6
+ from tap.tracker import Tracker
7
+
8
+ SHOW_CAPTURE_LOG = ("log", "all")
9
+ SHOW_CAPTURE_OUT = ("stdout", "all")
10
+ SHOW_CAPTUER_ERR = ("stderr", "all")
11
+
12
+
13
+ class TAPPlugin:
14
+ def __init__(self, config: pytest.Config) -> None:
15
+ self._tracker = Tracker(
16
+ outdir=config.option.tap_outdir,
17
+ combined=config.option.tap_combined,
18
+ streaming=config.option.tap_stream,
19
+ stream=sys.stdout,
20
+ )
21
+
22
+ if self._tracker.streaming:
23
+ reporter = config.pluginmanager.getplugin("terminalreporter")
24
+ if reporter:
25
+ config.pluginmanager.unregister(reporter)
26
+ # A common pytest pattern is to use test functions without classes.
27
+ # The header looks really dumb for that pattern because it puts
28
+ # out a lot of line noise since every function gets its own header.
29
+ # Disable it automatically for streaming.
30
+ self._tracker.header = False
31
+
32
+ self.show_capture = config.option.showcapture
33
+ self.log_passing_tests = config.option.tap_log_passing_tests
34
+
35
+ @pytest.hookimpl()
36
+ def pytest_runtestloop(self, session):
37
+ """Output the plan line first."""
38
+ option = session.config.option
39
+ if option.tap_stream or option.tap_combined:
40
+ self._tracker.set_plan(session.testscollected)
41
+
42
+ @pytest.hookimpl(optionalhook=True)
43
+ def pytest_xdist_node_collection_finished(self, node, ids):
44
+ """Output the plan line first when using xdist."""
45
+ if self._tracker.streaming or self._tracker.combined:
46
+ self._tracker.set_plan(len(ids))
47
+
48
+ @pytest.hookimpl()
49
+ def pytest_runtest_logreport(self, report: pytest.TestReport):
50
+ """Add a test result to the tracker."""
51
+ is_trackable_result = (
52
+ (report.when == "setup" and report.outcome == "skipped")
53
+ or (report.when == "setup" and report.outcome == "failed")
54
+ or report.when == "call"
55
+ )
56
+ if not is_trackable_result:
57
+ return
58
+
59
+ description = str(report.location[0]) + "::" + str(report.location[2])
60
+ testcase = report.location[0]
61
+
62
+ # Handle xfails first because they report in unusual ways.
63
+ # Non-strict xfails will include `wasxfail` while strict xfails won't.
64
+ if hasattr(report, "wasxfail"):
65
+ reason = ""
66
+ # pytest adds an ugly "reason: " for expectedFailure
67
+ # even though the standard library doesn't accept a reason
68
+ # for that decorator.
69
+ # Ignore the "reason: " from pytest.
70
+ if report.wasxfail and report.wasxfail != "reason: ":
71
+ reason = f": {report.wasxfail}"
72
+
73
+ if report.skipped:
74
+ directive = f"TODO expected failure{reason}"
75
+ self._tracker.add_not_ok(testcase, description, directive=directive)
76
+ elif report.passed:
77
+ directive = f"TODO unexpected success{reason}"
78
+ self._tracker.add_ok(testcase, description, directive=directive)
79
+ elif report.passed:
80
+ diagnostics = None
81
+ if self.log_passing_tests:
82
+ diagnostics = _make_as_diagnostics(report, self.show_capture)
83
+ self._tracker.add_ok(testcase, description, diagnostics=diagnostics)
84
+ elif report.failed:
85
+ diagnostics = _make_as_diagnostics(report, self.show_capture)
86
+
87
+ # pytest treats an unexpected success from unitest.expectedFailure
88
+ # as a failure.
89
+ # To match up with TAPTestResult and the TAP spec, treat the pass
90
+ # as an ok with a todo directive instead.
91
+ if "Unexpected success" in str(report.longrepr):
92
+ self._tracker.add_ok(
93
+ testcase, description, directive="TODO unexpected success"
94
+ )
95
+ return
96
+
97
+ # A strict xfail that passes (i.e., XPASS) should be marked as a failure.
98
+ # The only indicator that strict xfail occurred
99
+ # for XPASS is to check longrepr.
100
+ if (
101
+ isinstance(report.longrepr, str)
102
+ and "[XPASS(strict)]" in report.longrepr
103
+ ):
104
+ self._tracker.add_not_ok(
105
+ testcase,
106
+ description,
107
+ directive=f"unexpected success: {report.longrepr}",
108
+ )
109
+ return
110
+
111
+ self._tracker.add_not_ok(testcase, description, diagnostics=diagnostics)
112
+ elif report.skipped:
113
+ reason = report.longrepr[2].split(":", 1)[1].strip() # type: ignore
114
+ self._tracker.add_skip(testcase, description, reason)
115
+
116
+ @pytest.hookimpl()
117
+ def pytest_unconfigure(self, config: pytest.Config):
118
+ """Dump the results."""
119
+ self._tracker.generate_tap_reports()
120
+
121
+
122
+ def pytest_addoption(parser):
123
+ """Include all the command line options."""
124
+ group = parser.getgroup("terminal reporting", "reporting", after="general")
125
+ group.addoption(
126
+ "--tap",
127
+ default=False,
128
+ dest="tap_stream",
129
+ action="store_true",
130
+ help="Stream TAP output instead of the default test runner output.",
131
+ )
132
+ # Deprecated, but keeping for backwards compatibility.
133
+ group.addoption(
134
+ "--tap-stream", default=False, action="store_true", help=argparse.SUPPRESS
135
+ )
136
+ group.addoption(
137
+ "--tap-files",
138
+ default=False,
139
+ action="store_true",
140
+ help="Store all TAP test results into individual files per test case.",
141
+ )
142
+ group.addoption(
143
+ "--tap-combined",
144
+ default=False,
145
+ action="store_true",
146
+ help="Store all TAP test results into a combined output file.",
147
+ )
148
+ group.addoption(
149
+ "--tap-outdir",
150
+ metavar="path",
151
+ help=(
152
+ "An optional output directory to write TAP files to. "
153
+ "If the directory does not exist, it will be created."
154
+ ),
155
+ )
156
+ group.addoption(
157
+ "--tap-log-passing-tests",
158
+ default=False,
159
+ action="store_true",
160
+ help="Capture log information for passing tests to TAP report",
161
+ )
162
+
163
+
164
+ @pytest.hookimpl(trylast=True)
165
+ def pytest_configure(config: pytest.Config) -> None:
166
+ """Enable the plugin if the TAP flags are used."""
167
+ # The help printing uses the terminalreporter,
168
+ # which is unregistered by the streaming mode.
169
+ if config.option.help:
170
+ return
171
+
172
+ if (
173
+ config.option.tap_stream
174
+ or config.option.tap_combined
175
+ or config.option.tap_files
176
+ ):
177
+ config.pluginmanager.register(TAPPlugin(config), "tapplugin")
178
+
179
+
180
+ def _make_as_diagnostics(report, show_capture):
181
+ """Format a report as TAP diagnostic output."""
182
+ lines = report.longreprtext.splitlines(keepends=True)
183
+
184
+ if show_capture in SHOW_CAPTURE_LOG:
185
+ if lines:
186
+ lines[-1] += "\n"
187
+ lines += ["--- Captured Log ---\n"] + (
188
+ report.caplog.splitlines(keepends=True) or [""]
189
+ )
190
+ if show_capture in SHOW_CAPTURE_OUT:
191
+ if lines:
192
+ lines[-1] += "\n"
193
+ lines += ["--- Captured Out ---\n"] + (
194
+ report.capstdout.splitlines(keepends=True) or [""]
195
+ )
196
+ if show_capture in SHOW_CAPTUER_ERR:
197
+ if lines:
198
+ lines[-1] += "\n"
199
+ lines += ["--- Captured Err ---\n"] + (
200
+ report.capstderr.splitlines(keepends=True) or [""]
201
+ )
202
+
203
+ return format_as_diagnostics(lines)
File without changes
@@ -0,0 +1,24 @@
1
+ def test_includes_options(testdir):
2
+ """All options are present in the help."""
3
+ result = testdir.runpytest("--help")
4
+
5
+ expected_option_flags = [
6
+ "*--tap *",
7
+ "*--tap-files*",
8
+ "*--tap-combined*",
9
+ "*--tap-outdir=path*",
10
+ "*--tap-log-passing-tests*",
11
+ ]
12
+ result.stdout.fnmatch_lines(expected_option_flags)
13
+
14
+
15
+ def test_handle_help_with_stream(testdir):
16
+ """The help prints when the stream option is given.
17
+
18
+ This demonstrates behavior reported in:
19
+ https://github.com/python-tap/pytest-tap/issues/59
20
+ """
21
+ result = testdir.runpytest("--tap", "-h")
22
+
23
+ expected_option_flags = ["*--tap *"]
24
+ result.stdout.fnmatch_lines(expected_option_flags)
@@ -0,0 +1,247 @@
1
+ from tap.tracker import ENABLE_VERSION_13
2
+
3
+
4
+ def test_stream(testdir, sample_test_file):
5
+ """Results are streamed to stdout."""
6
+ result = testdir.runpytest_subprocess("--tap-stream")
7
+
8
+ result.stdout.fnmatch_lines(
9
+ [
10
+ "1..6",
11
+ "ok 1 test_stream.py::test_ok",
12
+ "not ok 2 test_stream.py::test_not_ok",
13
+ "ok 3 test_stream.py::test_params[foo]",
14
+ "ok 4 test_stream.py::test_params[bar]",
15
+ "ok 5 test_stream.py::test_skipped # SKIP some reason",
16
+ "not ok 6 test_stream.py::test_broken # TODO expected failure: a reason",
17
+ ]
18
+ )
19
+
20
+
21
+ def test_stream_simple_flag(testdir, sample_test_file):
22
+ """Results are streamed to stdout when the shorter streaming flag."""
23
+ result = testdir.runpytest_subprocess("--tap")
24
+
25
+ result.stdout.fnmatch_lines(
26
+ [
27
+ "1..6",
28
+ "ok 1 test_stream_simple_flag.py::test_ok",
29
+ "not ok 2 test_stream_simple_flag.py::test_not_ok",
30
+ "ok 3 test_stream_simple_flag.py::test_params[foo]",
31
+ "ok 4 test_stream_simple_flag.py::test_params[bar]",
32
+ "ok 5 test_stream_simple_flag.py::test_skipped # SKIP some reason",
33
+ "not ok 6 test_stream_simple_flag.py::test_broken "
34
+ "# TODO expected failure: a reason",
35
+ ]
36
+ )
37
+
38
+
39
+ def test_combined(testdir, sample_test_file):
40
+ """Tests are combined into a single output file."""
41
+ testdir.runpytest_subprocess("--tap-combined")
42
+
43
+ testresults = testdir.tmpdir.join("testresults.tap")
44
+ assert testresults.check()
45
+ actual_results = [
46
+ line.strip() for line in testresults.readlines() if not line.startswith("#")
47
+ ]
48
+ expected_results = [
49
+ "1..6",
50
+ "ok 1 test_combined.py::test_ok",
51
+ "not ok 2 test_combined.py::test_not_ok",
52
+ "ok 3 test_combined.py::test_params[foo]",
53
+ "ok 4 test_combined.py::test_params[bar]",
54
+ "ok 5 test_combined.py::test_skipped # SKIP some reason",
55
+ "not ok 6 test_combined.py::test_broken # TODO expected failure: a reason",
56
+ ]
57
+ # If the dependencies for version 13 happen to be installed, tweak the output.
58
+ if ENABLE_VERSION_13:
59
+ expected_results.insert(0, "TAP version 13")
60
+ assert actual_results == expected_results
61
+
62
+
63
+ def test_files(testdir, sample_test_file):
64
+ """Tests are split into separate files."""
65
+ testdir.makepyfile(
66
+ test_other_file="""
67
+ def test_other_ok():
68
+ assert True
69
+ """
70
+ )
71
+
72
+ testdir.runpytest_subprocess("--tap-files")
73
+
74
+ sample_tap = testdir.tmpdir.join("test_files.py.tap")
75
+ assert sample_tap.check()
76
+ other_tap = testdir.tmpdir.join("test_other_file.py.tap")
77
+ assert other_tap.check()
78
+
79
+
80
+ def test_outdir(testdir, sample_test_file):
81
+ """Tests are put in the output directory."""
82
+ testdir.runpytest_subprocess("--tap-outdir", "results", "--tap-combined")
83
+
84
+ outdir = testdir.tmpdir.join("results")
85
+ testresults = outdir.join("testresults.tap")
86
+ assert testresults.check()
87
+
88
+
89
+ def test_logging(testdir, sample_test_file):
90
+ """Test logs are added to TAP diagnostics."""
91
+ result = testdir.runpytest_subprocess("--tap")
92
+ result.stdout.fnmatch_lines(
93
+ [
94
+ "# --- Captured Log ---",
95
+ "*Running test_not_ok*",
96
+ "# --- Captured Out ---",
97
+ "# --- Captured Err ---",
98
+ ]
99
+ )
100
+ result.stdout.no_fnmatch_line("*Running test_ok*")
101
+
102
+
103
+ def test_log_passing_tests(testdir, sample_test_file):
104
+ """Test logs are added to TAP diagnostics."""
105
+ result = testdir.runpytest_subprocess(
106
+ "--tap", "--tap-log-passing-tests", "--log-level", "INFO"
107
+ )
108
+ result.stdout.fnmatch_lines(
109
+ [
110
+ "# --- Captured Log ---",
111
+ "*Running test_ok*",
112
+ ]
113
+ )
114
+ result.stdout.no_fnmatch_line("*Debug logging info*")
115
+
116
+
117
+ def test_xfail_no_reason(testdir):
118
+ """xfails output gracefully when no reason is provided."""
119
+ testdir.makepyfile(
120
+ """
121
+ import pytest
122
+
123
+ @pytest.mark.xfail(strict=False)
124
+ def test_unexpected_success():
125
+ assert True
126
+
127
+ @pytest.mark.xfail(strict=False)
128
+ def test_expected_failure():
129
+ assert False
130
+ """
131
+ )
132
+ result = testdir.runpytest_subprocess("--tap")
133
+
134
+ result.stdout.fnmatch_lines(
135
+ [
136
+ "ok 1 test_xfail_no_reason.py::test_unexpected_success "
137
+ "# TODO unexpected success",
138
+ "not ok 2 test_xfail_no_reason.py::test_expected_failure "
139
+ "# TODO expected failure",
140
+ ]
141
+ )
142
+
143
+
144
+ def test_xfail_nonstrict(testdir):
145
+ """Non-strict xfails are treated as TODO directives."""
146
+ testdir.makepyfile(
147
+ """
148
+ import pytest
149
+
150
+ @pytest.mark.xfail(strict=False, reason='a reason')
151
+ def test_unexpected_success():
152
+ assert True
153
+
154
+ @pytest.mark.xfail(strict=False, reason='a reason')
155
+ def test_expected_failure():
156
+ assert False
157
+ """
158
+ )
159
+ result = testdir.runpytest_subprocess("--tap")
160
+
161
+ result.stdout.fnmatch_lines(
162
+ [
163
+ "ok 1 test_xfail_nonstrict.py::test_unexpected_success "
164
+ "# TODO unexpected success: a reason",
165
+ "not ok 2 test_xfail_nonstrict.py::test_expected_failure "
166
+ "# TODO expected failure: a reason",
167
+ ]
168
+ )
169
+
170
+
171
+ def test_xfail_strict(testdir):
172
+ """xfail strict mode handles expected behavior."""
173
+ testdir.makepyfile(
174
+ """
175
+ import pytest
176
+
177
+ @pytest.mark.xfail(strict=True, reason='a reason')
178
+ def test_unexpected_success():
179
+ assert True
180
+
181
+ @pytest.mark.xfail(strict=True, reason='a reason')
182
+ def test_expected_failure():
183
+ assert False
184
+ """
185
+ )
186
+ result = testdir.runpytest_subprocess("--tap")
187
+
188
+ result.stdout.fnmatch_lines(
189
+ [
190
+ "not ok 1 test_xfail_strict.py::test_unexpected_success "
191
+ "# unexpected success: [XPASS(strict)] a reason",
192
+ "not ok 2 test_xfail_strict.py::test_expected_failure "
193
+ "# TODO expected failure: a reason",
194
+ ]
195
+ )
196
+
197
+
198
+ def test_unittest_expected_failure(testdir):
199
+ """The plugin handles unittest's expectedFailure decorator behavior."""
200
+ testdir.makepyfile(
201
+ """
202
+ import pytest
203
+ import unittest
204
+
205
+ class TestExpectedFailure(unittest.TestCase):
206
+ @unittest.expectedFailure
207
+ def test_when_failing(self):
208
+ assert False
209
+
210
+ @unittest.expectedFailure
211
+ def test_when_passing(self):
212
+ assert True
213
+ """
214
+ )
215
+ result = testdir.runpytest_subprocess("--tap")
216
+
217
+ expected = [
218
+ "not ok 1 test_unittest_expected_failure.py::"
219
+ "TestExpectedFailure.test_when_failing # TODO expected failure",
220
+ "ok 2 test_unittest_expected_failure.py::"
221
+ "TestExpectedFailure.test_when_passing # TODO unexpected success",
222
+ ]
223
+ result.stdout.fnmatch_lines(expected)
224
+
225
+
226
+ def test_setup_failure(testdir):
227
+ """A failure in test setup is marked as an error.
228
+
229
+ See https://github.com/python-tap/pytest-tap/issues/39.
230
+ """
231
+ testdir.makepyfile(
232
+ """
233
+ import pytest
234
+
235
+ @pytest.fixture
236
+ def bad_fixture():
237
+ raise Exception('boom')
238
+
239
+ def test_with_bad_fixture(bad_fixture):
240
+ assert True
241
+ """
242
+ )
243
+ result = testdir.runpytest_subprocess("--tap")
244
+
245
+ result.stdout.fnmatch_lines(
246
+ ["1..1", "not ok 1 test_setup_failure.py::test_with_bad_fixture"]
247
+ )
@@ -0,0 +1,30 @@
1
+ """The xdist plugin isn't installed so these tests check against the hook contract."""
2
+
3
+ from unittest import mock
4
+
5
+ import pytest
6
+
7
+ from pytest_tap.plugin import TAPPlugin
8
+
9
+
10
+ @pytest.mark.parametrize(
11
+ "combined, stream, expected",
12
+ [
13
+ (False, True, True),
14
+ (True, False, True),
15
+ (False, False, False),
16
+ ],
17
+ )
18
+ def test_sets_plan_streaming(combined, stream, expected):
19
+ """The plan is set for the given mode conditions by a node's collection report."""
20
+ config = mock.Mock()
21
+ config.option.tap_outdir = "."
22
+ config.option.tap_combined = combined
23
+ config.option.tap_stream = stream
24
+ plugin = TAPPlugin(config)
25
+ node = mock.Mock()
26
+ test_ids = ["a", "b", "c"]
27
+
28
+ plugin.pytest_xdist_node_collection_finished(node, test_ids)
29
+
30
+ assert (plugin._tracker.plan == 3) is expected
@@ -1,4 +0,0 @@
1
- include AUTHORS
2
- include LICENSE
3
- include README.rst
4
- include docs/releases.rst
pytest-tap-3.3/README.rst DELETED
@@ -1,57 +0,0 @@
1
- pytest-tap
2
- ==========
3
-
4
- |version| |license| |travis| |travismac| |appveyor| |coverage|
5
-
6
- .. |version| image:: https://img.shields.io/pypi/v/pytest-tap.svg
7
- :target: https://pypi.python.org/pypi/pytest-tap
8
- :alt: PyPI version
9
- .. |license| image:: https://img.shields.io/pypi/l/pytest-tap.svg
10
- :target: https://raw.githubusercontent.com/python-tap/pytest-tap/master/LICENSE
11
- :alt: BSD license
12
- .. |coverage| image:: https://img.shields.io/codecov/c/github/python-tap/pytest-tap.svg
13
- :target: https://codecov.io/github/python-tap/pytest-tap
14
- :alt: Coverage
15
-
16
- Test Anything Protocol (TAP) reporting plugin for
17
- `pytest <http://pytest.org/latest/>`_
18
-
19
- The plugin outputs test results as TAP data in a variety of formats.
20
- See the `tappy documentation <http://tappy.readthedocs.io/en/latest/producers.html#pytest-tap-plugin>`_
21
- for more information on usage.
22
-
23
- Install
24
- -------
25
-
26
- .. code-block:: console
27
-
28
- $ pip install pytest-tap
29
-
30
- Usage
31
- -----
32
-
33
- This is an example usage from the plugin's test suite.
34
-
35
- .. code-block:: console
36
-
37
- $ pytest --tap-stream
38
- 1..12
39
- ok 1 - TestPlugin.test_generates_reports_for_combined
40
- ok 2 - TestPlugin.test_generates_reports_for_files
41
- ok 3 - TestPlugin.test_generates_reports_for_stream
42
- ok 4 - TestPlugin.test_includes_options
43
- ok 5 - TestPlugin.test_skips_reporting_with_no_output_option
44
- ok 6 - TestPlugin.test_track_when_call_report
45
- ok 7 - TestPlugin.test_tracker_combined_set
46
- ok 8 - TestPlugin.test_tracker_outdir_set
47
- ok 9 - TestPlugin.test_tracker_stream_set
48
- ok 10 - TestPlugin.test_tracks_not_ok
49
- ok 11 - TestPlugin.test_tracks_ok
50
- ok 12 - TestPlugin.test_tracks_skip
51
-
52
- Contributing
53
- ------------
54
-
55
- The project welcomes contributions of all kinds.
56
- Check out the `contributing guidelines <https://github.com/python-tap/pytest-tap/blob/master/.github/contributing.md>`_
57
- for tips on how to get started.
@@ -1,81 +0,0 @@
1
- Releases
2
- ========
3
-
4
- Version 3.3, October 27, 2021
5
- -----------------------------
6
-
7
- * Add support for Python 3.10.
8
- * Fix bug with help printing when streaming mode is enabled (#59).
9
- * Drop support for Python 3.5 (it is end-of-life).
10
- * Remove unmaintained (and likely inaccurate) locale info.
11
-
12
- Version 3.2, November 7, 2020
13
- -----------------------------
14
-
15
- * Add support for Python 3.8.
16
- * Add support for Python 3.9.
17
- * Handle ``unittest.expectedFailure`` and ``pytest.xfail``
18
- in a way that is more consistent
19
- with the TAP specification.
20
-
21
- Version 3.1, Released March 25, 2020
22
- ------------------------------------
23
-
24
- * Fix reporting that was broken by the removal
25
- of a deprecated feature in pytest 5.4.
26
-
27
- Version 3.0, Released January 28, 2020
28
- --------------------------------------
29
-
30
- * Drop support for Python 2.
31
-
32
- Version 2.5, Released December 26, 2019
33
- ---------------------------------------
34
-
35
- * Last supported version for Python 2.
36
- * Pin tap.py to a version range that supports Python 2.
37
-
38
- Version 2.4, Released October 21, 2019
39
- --------------------------------------
40
-
41
- * Handle failures that occur from setup.
42
- This will catch errors that may happen in fixtures.
43
- * Drop support for Python 3.4 (it is end-of-life).
44
- * Add support for Python 3.7.
45
-
46
- Version 2.3, Released September 16, 2018
47
- ----------------------------------------
48
-
49
- * Improve xfail handling.
50
- Honor strict xfail mode.
51
- Use TODO instead of SKIP directive to better align with TAP specification.
52
- * Output the plan line (``1..N``) first.
53
-
54
- Version 2.2, Released January 9, 2018
55
- -------------------------------------
56
-
57
- * Update output format to match closer to pytest styling.
58
- * Drop support for Python 3.3 (it is end-of-life).
59
-
60
- Version 2.1, Released August 12, 2017
61
- -------------------------------------
62
-
63
- * Add support for Python 3.6.
64
- * Fix crash when running with pytest-xdist (#27).
65
-
66
- Version 2.0, Released August 1, 2016
67
- ------------------------------------
68
-
69
- * Update to tap.py 2.0.
70
- This update drops the indirect dependencies on nose and pygments.
71
- * Improve handling of skips and xfails.
72
- * Suppress ``# TAP results for TestCase`` for streaming.
73
- This header makes little sense for pytest's test function paradigm.
74
- Including the header generated extra noise for each function.
75
- * Drop support for Python 2.6
76
-
77
- Version 1.9, Released June 11, 2016
78
- -----------------------------------
79
-
80
- * Initial release as stand-alone plugin.
81
- The version number aligns with tappy.
pytest-tap-3.3/setup.cfg DELETED
@@ -1,21 +0,0 @@
1
- [bumpversion]
2
- current_version = 3.3
3
- commit = True
4
- tag = True
5
- parse = (?P<major>\d+)\.(?P<minor>\d+)
6
- serialize = {major}.{minor}
7
-
8
- [bumpversion:file:setup.py]
9
-
10
- [bumpversion:file:src/pytest_tap/__init__.py]
11
-
12
- [metadata]
13
- license-file = LICENSE
14
-
15
- [flake8]
16
- max-line-length = 88
17
-
18
- [egg_info]
19
- tag_build =
20
- tag_date = 0
21
-
pytest-tap-3.3/setup.py DELETED
@@ -1,51 +0,0 @@
1
- """
2
- pytest-tap is a reporting plugin for pytest that outputs
3
- `Test Anything Protocol (TAP) <http://testanything.org/>`_ data.
4
- TAP is a line based test protocol for recording test data in a standard way.
5
-
6
- Follow development on `GitHub <https://github.com/python-tap/pytest-tap>`_.
7
- Developer documentation is on
8
- `Read the Docs <https://tappy.readthedocs.io/>`_.
9
- """
10
-
11
- from setuptools import find_packages, setup
12
-
13
-
14
- if __name__ == "__main__":
15
- with open("docs/releases.rst", "r") as f:
16
- releases = f.read()
17
-
18
- long_description = __doc__ + "\n\n" + releases
19
-
20
- setup(
21
- name="pytest-tap",
22
- version="3.3",
23
- url="https://github.com/python-tap/pytest-tap",
24
- license="BSD",
25
- author="Matt Layman",
26
- author_email="matthewlayman@gmail.com",
27
- description="Test Anything Protocol (TAP) reporting plugin for pytest",
28
- long_description=long_description,
29
- packages=find_packages(where="src"),
30
- package_dir={"": "src"},
31
- entry_points={"pytest11": ["tap = pytest_tap.plugin"]},
32
- include_package_data=True,
33
- zip_safe=False,
34
- platforms="any",
35
- install_requires=["pytest>=3.0", "tap.py>=3.0,<4.0"],
36
- classifiers=[
37
- "Development Status :: 5 - Production/Stable",
38
- "Framework :: Pytest",
39
- "Intended Audience :: Developers",
40
- "License :: OSI Approved :: BSD License",
41
- "Operating System :: OS Independent",
42
- "Programming Language :: Python :: 3.6",
43
- "Programming Language :: Python :: 3.7",
44
- "Programming Language :: Python :: 3.8",
45
- "Programming Language :: Python :: 3.9",
46
- "Programming Language :: Python :: 3.10",
47
- "Programming Language :: Python :: Implementation :: PyPy",
48
- "Topic :: Software Development :: Testing",
49
- ],
50
- keywords=["TAP", "unittest", "pytest"],
51
- )
@@ -1 +0,0 @@
1
- __version__ = "3.3"
@@ -1,150 +0,0 @@
1
- import sys
2
-
3
- import pytest
4
- from tap.formatter import format_as_diagnostics
5
- from tap.tracker import Tracker
6
-
7
- # Because of how pytest hooks work, there is not much choice
8
- # except to use module level state. Ugh.
9
- tracker = Tracker()
10
- ENABLED = False
11
-
12
-
13
- def pytest_addoption(parser):
14
- """Include all the command line options."""
15
- group = parser.getgroup("terminal reporting", "reporting", after="general")
16
- group.addoption(
17
- "--tap-stream",
18
- default=False,
19
- action="store_true",
20
- help="Stream TAP output instead of the default test runner output.",
21
- )
22
- group.addoption(
23
- "--tap-files",
24
- default=False,
25
- action="store_true",
26
- help="Store all TAP test results into individual files per test case.",
27
- )
28
- group.addoption(
29
- "--tap-combined",
30
- default=False,
31
- action="store_true",
32
- help="Store all TAP test results into a combined output file.",
33
- )
34
- group.addoption(
35
- "--tap-outdir",
36
- metavar="path",
37
- help=(
38
- "An optional output directory to write TAP files to. "
39
- "If the directory does not exist, it will be created."
40
- ),
41
- )
42
-
43
-
44
- @pytest.mark.trylast
45
- def pytest_configure(config):
46
- """Set all the options before the test run."""
47
- # The help printing uses the terminalreporter,
48
- # which is unregistered by the streaming mode.
49
- if config.option.help:
50
- return
51
-
52
- global ENABLED
53
- ENABLED = (
54
- config.option.tap_stream
55
- or config.option.tap_combined
56
- or config.option.tap_files
57
- )
58
-
59
- tracker.outdir = config.option.tap_outdir
60
- tracker.combined = config.option.tap_combined
61
- if config.option.tap_stream:
62
- reporter = config.pluginmanager.getplugin("terminalreporter")
63
- if reporter:
64
- config.pluginmanager.unregister(reporter)
65
- tracker.streaming = True
66
- tracker.stream = sys.stdout
67
- # A common pytest pattern is to use test functions without classes.
68
- # The header looks really dumb for that pattern because it puts
69
- # out a lot of line noise since every function gets its own header.
70
- # Disable it automatically for streaming.
71
- tracker.header = False
72
-
73
-
74
- def pytest_runtestloop(session):
75
- """Output the plan line first."""
76
- option = session.config.option
77
- if ENABLED and (option.tap_stream or option.tap_combined):
78
- tracker.set_plan(session.testscollected)
79
-
80
-
81
- def pytest_runtest_logreport(report):
82
- """Add a test result to the tracker."""
83
- if not ENABLED:
84
- return
85
-
86
- is_trackable_result = (
87
- (report.when == "setup" and report.outcome == "skipped")
88
- or (report.when == "setup" and report.outcome == "failed")
89
- or report.when == "call"
90
- )
91
- if not is_trackable_result:
92
- return
93
-
94
- description = str(report.location[0]) + "::" + str(report.location[2])
95
- testcase = report.location[0]
96
-
97
- # Handle xfails first because they report in unusual ways.
98
- # Non-strict xfails will include `wasxfail` while strict xfails won't.
99
- if hasattr(report, "wasxfail"):
100
- reason = ""
101
- # pytest adds an ugly "reason: " for expectedFailure
102
- # even though the standard library doesn't accept a reason for that decorator.
103
- # Ignore the "reason: " from pytest.
104
- if report.wasxfail and report.wasxfail != "reason: ":
105
- reason = ": {}".format(report.wasxfail)
106
-
107
- if report.skipped:
108
- directive = "TODO expected failure{}".format(reason)
109
- tracker.add_not_ok(testcase, description, directive=directive)
110
- elif report.passed:
111
- directive = "TODO unexpected success{}".format(reason)
112
- tracker.add_ok(testcase, description, directive=directive)
113
- elif report.passed:
114
- tracker.add_ok(testcase, description)
115
- elif report.failed:
116
- diagnostics = _make_as_diagnostics(report)
117
-
118
- # pytest treats an unexpected success from unitest.expectedFailure as a failure.
119
- # To match up with TAPTestResult and the TAP spec, treat the pass
120
- # as an ok with a todo directive instead.
121
- if isinstance(report.longrepr, str) and "Unexpected success" in report.longrepr:
122
- tracker.add_ok(testcase, description, directive="TODO unexpected success")
123
- return
124
-
125
- # A strict xfail that passes (i.e., XPASS) should be marked as a failure.
126
- # The only indicator that strict xfail occurred for XPASS is to check longrepr.
127
- if isinstance(report.longrepr, str) and "[XPASS(strict)]" in report.longrepr:
128
- tracker.add_not_ok(
129
- testcase,
130
- description,
131
- directive="unexpected success: {}".format(report.longrepr),
132
- )
133
- return
134
-
135
- tracker.add_not_ok(testcase, description, diagnostics=diagnostics)
136
- elif report.skipped:
137
- reason = report.longrepr[2].split(":", 1)[1].strip()
138
- tracker.add_skip(testcase, description, reason)
139
-
140
-
141
- def _make_as_diagnostics(report):
142
- """Format a report as TAP diagnostic output."""
143
- lines = report.longreprtext.splitlines(keepends=True)
144
- return format_as_diagnostics(lines)
145
-
146
-
147
- def pytest_unconfigure(config):
148
- """Dump the results."""
149
- if ENABLED:
150
- tracker.generate_tap_reports()
@@ -1,16 +0,0 @@
1
- AUTHORS
2
- LICENSE
3
- MANIFEST.in
4
- README.rst
5
- setup.cfg
6
- setup.py
7
- docs/releases.rst
8
- src/pytest_tap/__init__.py
9
- src/pytest_tap/plugin.py
10
- src/pytest_tap.egg-info/PKG-INFO
11
- src/pytest_tap.egg-info/SOURCES.txt
12
- src/pytest_tap.egg-info/dependency_links.txt
13
- src/pytest_tap.egg-info/entry_points.txt
14
- src/pytest_tap.egg-info/not-zip-safe
15
- src/pytest_tap.egg-info/requires.txt
16
- src/pytest_tap.egg-info/top_level.txt
@@ -1,3 +0,0 @@
1
- [pytest11]
2
- tap = pytest_tap.plugin
3
-
@@ -1,2 +0,0 @@
1
- pytest>=3.0
2
- tap.py<4.0,>=3.0
@@ -1 +0,0 @@
1
- pytest_tap
File without changes