Skip to content

Commit 7adbe38

Browse files
committed
First version
1 parent 6fa60e3 commit 7adbe38

File tree

12 files changed

+397
-2
lines changed

12 files changed

+397
-2
lines changed

.github/workflows/main.yml

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
name: build
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
build:
7+
8+
runs-on: ${{ matrix.os }}
9+
10+
strategy:
11+
fail-fast: false
12+
matrix:
13+
python: ["3.5", "3.6", "3.7", "3.8"]
14+
os: [ubuntu-latest, windows-latest]
15+
include:
16+
- python: "3.5"
17+
tox_env: "py35"
18+
- python: "3.6"
19+
tox_env: "py36"
20+
- python: "3.7"
21+
tox_env: "py37"
22+
- python: "3.8"
23+
tox_env: "py38"
24+
25+
steps:
26+
- uses: actions/checkout@v1
27+
- name: Set up Python
28+
uses: actions/setup-python@v1
29+
with:
30+
python-version: ${{ matrix.python }}
31+
- name: Install tox
32+
run: |
33+
python -m pip install --upgrade pip
34+
pip install tox
35+
- name: Test
36+
run: |
37+
tox -e ${{ matrix.tox_env }}
38+
39+
linting:
40+
41+
runs-on: ubuntu-latest
42+
43+
steps:
44+
- uses: actions/checkout@v1
45+
- name: Set up Python
46+
uses: actions/setup-python@v1
47+
with:
48+
python-version: "3.7"
49+
- name: Install tox
50+
run: |
51+
python -m pip install --upgrade pip
52+
pip install tox
53+
- name: Linting
54+
run: |
55+
tox -e linting
56+
57+
deploy:
58+
59+
runs-on: ubuntu-latest
60+
61+
needs: [build, linting]
62+
63+
steps:
64+
- uses: actions/checkout@v1
65+
- name: Set up Python
66+
uses: actions/setup-python@v1
67+
with:
68+
python-version: "3.7"
69+
- name: Install wheel
70+
run: |
71+
python -m pip install --upgrade pip
72+
pip install wheel
73+
- name: Build package
74+
run: |
75+
python setup.py sdist bdist_wheel
76+
- name: Publish package to PyPI
77+
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
78+
uses: pypa/gh-action-pypi-publish@master
79+
with:
80+
user: __token__
81+
password: ${{ secrets.pypi_token }}

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,5 @@ venv.bak/
102102

103103
# mypy
104104
.mypy_cache/
105+
106+
src/pytest_reportlog/_version.py

.pre-commit-config.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
exclude: '^($|.*\.bin)'
2+
repos:
3+
- repo: https://github.com/ambv/black
4+
rev: 19.3b0
5+
hooks:
6+
- id: black
7+
args: [--safe, --quiet]
8+
language_version: python3.7
9+
- repo: https://github.com/pre-commit/pre-commit-hooks
10+
rev: v2.2.3
11+
hooks:
12+
- id: trailing-whitespace
13+
- id: end-of-file-fixer
14+
- repo: local
15+
hooks:
16+
- id: rst
17+
name: rst
18+
entry: rst-lint --encoding utf-8
19+
files: ^(CHANGELOG.rst|README.rst|HOWTORELEASE.rst)$
20+
language: python
21+
additional_dependencies: [pygments, restructuredtext_lint]

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
0.1.0 (unreleased)
2+
------------------
3+
4+
* First version.

HOWTORELEASE.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Here are the steps on how to make a new release.
2+
3+
1. Create a ``release-VERSION`` branch from ``upstream/master``.
4+
2. Update ``CHANGELOG.rst``.
5+
3. Push a branch with the changes.
6+
4. Once all builds pass, push a tag to ``upstream``.
7+
5. Merge the PR.

README.md

Lines changed: 0 additions & 2 deletions
This file was deleted.

README.rst

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
================
2+
pytest-reportlog
3+
================
4+
5+
|python| |version| |anaconda| |ci| |black|
6+
7+
.. |version| image:: http://img.shields.io/pypi/v/pytest-reportlog.svg
8+
:target: https://pypi.python.org/pypi/pytest-reportlog
9+
10+
.. |anaconda| image:: https://img.shields.io/conda/vn/conda-forge/pytest-reportlog.svg
11+
:target: https://anaconda.org/conda-forge/pytest-reportlog
12+
13+
.. |ci| image:: https://github.com/pytest-dev/pytest-reportlog/workflows/build/badge.svg
14+
:target: https://github.com/pytest-dev/pytest-reportlog/actions
15+
16+
.. |python| image:: https://img.shields.io/pypi/pyversions/pytest-reportlog.svg
17+
:target: https://pypi.python.org/pypi/pytest-reportlog/
18+
19+
.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
20+
:target: https://github.com/ambv/black
21+
22+
Replacement for the ``--resultlog`` option, focused in simplicity and extensibility.
23+
24+
.. note::
25+
This plugin was created so developers can try out the candidate to replace the
26+
`deprecated --resultlog <https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log>`__ option.
27+
28+
If you use ``--resultlog``, please try out ``--report-log`` and provide feedback.
29+
30+
Usage
31+
=====
32+
33+
The ``--report-log=FILE`` option writes *report logs* into a file as the test session executes.
34+
35+
Each line of the report log contains a self contained JSON object corresponding to a testing event,
36+
such as a collection or a test result report. The file is guaranteed to be flushed after writing
37+
each line, so systems can read and process events in real-time.
38+
39+
Each JSON object contains a special key ``$report_type``, which contains a unique identifier for
40+
that kind of report object. For future compatibility, consumers of the file should ignore reports
41+
they don't recognize, as well as ignore unknown properties/keys in JSON objects that they do know,
42+
as future pytest versions might enrich the objects with more properties/keys.
43+
44+
45+
Example
46+
-------
47+
48+
Consider this file:
49+
50+
.. code-block:: python
51+
52+
# content of test_report_example.py
53+
54+
55+
def test_ok():
56+
assert 5 + 5 == 10
57+
58+
59+
def test_fail():
60+
assert 4 + 4 == 1
61+
62+
63+
::
64+
65+
$ pytest test_report_example.py -q --report-log=log.json
66+
.F [100%]
67+
================================= FAILURES =================================
68+
________________________________ test_fail _________________________________
69+
70+
def test_fail():
71+
> assert 4 + 4 == 1
72+
E assert (4 + 4) == 1
73+
74+
test_report_example.py:8: AssertionError
75+
------------------- generated report log file: log.json --------------------
76+
1 failed, 1 passed in 0.12s
77+
78+
The generated ``log.json`` will contain a JSON object per line:
79+
80+
::
81+
82+
$ cat log.json
83+
{"pytest_version": "5.2.2", "$report_type": "SessionStart"}
84+
{"nodeid": "", "outcome": "passed", "longrepr": null, "result": null, "sections": [], "_report_type": "CollectReport"}
85+
{"nodeid": ".tmp/test_report_example.py", "outcome": "passed", "longrepr": null, "result": null, "sections": [], "_report_type": "CollectReport"}
86+
{"nodeid": ".tmp/test_report_example.py::test_ok", "location": [".tmp\\test_report_example.py", 0, "test_ok"], "keywords": {"test_ok": 1, "pytest-reportlog": 1, ".tmp/test_report_example.py": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.0, "_report_type": "TestReport"}
87+
{"nodeid": ".tmp/test_report_example.py::test_ok", "location": [".tmp\\test_report_example.py", 0, "test_ok"], "keywords": {"test_ok": 1, "pytest-reportlog": 1, ".tmp/test_report_example.py": 1}, "outcome": "passed", "longrepr": null, "when": "call", "user_properties": [], "sections": [], "duration": 0.0, "_report_type": "TestReport"}
88+
{"nodeid": ".tmp/test_report_example.py::test_ok", "location": [".tmp\\test_report_example.py", 0, "test_ok"], "keywords": {"test_ok": 1, "pytest-reportlog": 1, ".tmp/test_report_example.py": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.00099945068359375, "_report_type": "TestReport"}
89+
{"nodeid": ".tmp/test_report_example.py::test_fail", "location": [".tmp\\test_report_example.py", 4, "test_fail"], "keywords": {"test_fail": 1, "pytest-reportlog": 1, ".tmp/test_report_example.py": 1}, "outcome": "passed", "longrepr": null, "when": "setup", "user_properties": [], "sections": [], "duration": 0.0, "_report_type": "TestReport"}
90+
{"nodeid": ".tmp/test_report_example.py::test_fail", "location": [".tmp\\test_report_example.py", 4, "test_fail"], "keywords": {"test_fail": 1, "pytest-reportlog": 1, ".tmp/test_report_example.py": 1}, "outcome": "failed", "longrepr": {"reprcrash": {"path": "D:\\projects\\pytest-reportlog\\.tmp\\test_report_example.py", "lineno": 6, "message": "assert (4 + 4) == 1"}, "reprtraceback": {"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def test_fail():", "> assert 4 + 4 == 1", "E assert (4 + 4) == 1"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": ".tmp\\test_report_example.py", "lineno": 6, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, "sections": [], "chain": [[{"reprentries": [{"type": "ReprEntry", "data": {"lines": [" def test_fail():", "> assert 4 + 4 == 1", "E assert (4 + 4) == 1"], "reprfuncargs": {"args": []}, "reprlocals": null, "reprfileloc": {"path": ".tmp\\test_report_example.py", "lineno": 6, "message": "AssertionError"}, "style": "long"}}], "extraline": null, "style": "long"}, {"path": "D:\\projects\\pytest-reportlog\\.tmp\\test_report_example.py", "lineno": 6, "message": "assert (4 + 4) == 1"}, null]]}, "when": "call", "user_properties": [], "sections": [], "duration": 0.0009992122650146484, "_report_type": "TestReport"}
91+
{"nodeid": ".tmp/test_report_example.py::test_fail", "location": [".tmp\\test_report_example.py", 4, "test_fail"], "keywords": {"test_fail": 1, "pytest-reportlog": 1, ".tmp/test_report_example.py": 1}, "outcome": "passed", "longrepr": null, "when": "teardown", "user_properties": [], "sections": [], "duration": 0.0, "_report_type": "TestReport"}
92+
{"exitstatus": 1, "$report_type": "SessionFinish"}
93+
94+
License
95+
=======
96+
97+
Distributed under the terms of the `MIT`_ license.
98+
99+
.. _MIT: https://github.com/pytest-dev/pytest-mock/blob/master/LICENSE

setup.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from io import open
2+
3+
from setuptools import setup, find_packages
4+
5+
with open("README.rst", encoding="utf-8") as f:
6+
readme = f.read()
7+
8+
setup(
9+
name="pytest-reportlog",
10+
entry_points={"pytest11": ["pytest_reportlog = pytest_reportlog.plugin"]},
11+
packages=find_packages(where="src"),
12+
package_dir={"": "src"},
13+
platforms="any",
14+
python_requires=">=3.5",
15+
install_requires=["pytest>=5.2"],
16+
use_scm_version={"write_to": "src/pytest_reportlog/_version.py"},
17+
setup_requires=["setuptools_scm"],
18+
url="https://github.com/pytest-dev/pytest-reportlog",
19+
license="MIT",
20+
author="Bruno Oliveira",
21+
author_email="[email protected]",
22+
description="Replacement for the --resultlog option, focused in simplicity and extensibility",
23+
long_description=readme,
24+
keywords="pytest",
25+
extras_require={"dev": ["pre-commit", "tox"]},
26+
classifiers=[
27+
"Development Status :: 3 - Alpha",
28+
"Framework :: Pytest",
29+
"Intended Audience :: Developers",
30+
"License :: OSI Approved :: MIT License",
31+
"Operating System :: OS Independent",
32+
"Programming Language :: Python :: 3",
33+
"Programming Language :: Python :: 3.5",
34+
"Programming Language :: Python :: 3.6",
35+
"Programming Language :: Python :: 3.7",
36+
"Programming Language :: Python :: 3.8",
37+
"Topic :: Software Development :: Testing",
38+
],
39+
)

src/pytest_reportlog/__init__.py

Whitespace-only changes.

src/pytest_reportlog/plugin.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import json
2+
from pathlib import Path
3+
4+
import pytest
5+
6+
7+
def pytest_addoption(parser):
8+
group = parser.getgroup("terminal reporting", "report-log plugin options")
9+
group.addoption(
10+
"--report-log",
11+
action="store",
12+
metavar="path",
13+
default=None,
14+
help="Path to line-based json objects of test session events.",
15+
)
16+
17+
18+
def pytest_configure(config):
19+
report_log = config.option.report_log
20+
if report_log and not hasattr(config, "slaveinput"):
21+
config._report_log_plugin = ReportLogPlugin(config, Path(report_log))
22+
config.pluginmanager.register(config._report_log_plugin)
23+
24+
25+
def pytest_unconfigure(config):
26+
report_log_plugin = getattr(config, "_report_log_plugin", None)
27+
if report_log_plugin:
28+
report_log_plugin.close()
29+
del config._report_log_plugin
30+
31+
32+
class ReportLogPlugin:
33+
def __init__(self, config, log_path: Path):
34+
self._config = config
35+
self._log_path = log_path
36+
37+
log_path.parent.mkdir(parents=True, exist_ok=True)
38+
self._file = log_path.open("w", buffering=1, encoding="UTF-8")
39+
40+
def close(self):
41+
if self._file is not None:
42+
self._file.close()
43+
self._file = None
44+
45+
def _write_json_data(self, data):
46+
self._file.write(json.dumps(data) + "\n")
47+
self._file.flush()
48+
49+
def pytest_sessionstart(self):
50+
data = {"pytest_version": pytest.__version__, "$report_type": "SessionStart"}
51+
self._write_json_data(data)
52+
53+
def pytest_sessionfinish(self, exitstatus):
54+
data = {"exitstatus": exitstatus, "$report_type": "SessionFinish"}
55+
self._write_json_data(data)
56+
57+
def pytest_runtest_logreport(self, report):
58+
data = self._config.hook.pytest_report_to_serializable(
59+
config=self._config, report=report
60+
)
61+
self._write_json_data(data)
62+
63+
def pytest_collectreport(self, report):
64+
data = self._config.hook.pytest_report_to_serializable(
65+
config=self._config, report=report
66+
)
67+
self._write_json_data(data)
68+
69+
def pytest_terminal_summary(self, terminalreporter):
70+
terminalreporter.write_sep(
71+
"-", "generated report log file: {}".format(self._log_path)
72+
)

0 commit comments

Comments
 (0)