diff --git a/.github/workflows/run-checks.yml b/.github/workflows/run-checks.yml index 16a76bd..889aa79 100644 --- a/.github/workflows/run-checks.yml +++ b/.github/workflows/run-checks.yml @@ -2,9 +2,7 @@ name: Run checks on: push: - branches: [ master ] pull_request: - branches: [ master ] jobs: tox: @@ -12,22 +10,34 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - environment: ["py36", "py37", "py36-emoji", "py37-emoji", "mypy", "flake8"] + environment: ["py36", "py37", + "py36-metadata", "py37-metadata", + "py36-emoji", "py37-emoji", + "py36-emoji-metadata", "py37-emoji-metadata" + "mypy", "flake8"] include: - environment: "py36" python: "3.6" - environment: "py37" python: "3.7" + - environment: "py36-metadata" + python: "3.6" + - environment: "py37-metadata" + python: "3.7" - environment: "py36-emoji" python: "3.6" - environment: "py37-emoji" python: "3.7" + - environment: "py36-emoji-metadata" + python: "3.6" + - environment: "py37-emoji-metadata" + python: "3.7" - environment: "mypy" python: "3.7" - environment: "flake8" python: "3.7" - container: + container: image: python:${{ matrix.python }} steps: diff --git a/COMMUNITY.md b/COMMUNITY.md index 2f68e87..775a342 100644 --- a/COMMUNITY.md +++ b/COMMUNITY.md @@ -3,7 +3,9 @@ - [@hackebrot] - [@seanson] - [@ssd71] +- [@jupe] [@seanson]: https://github.com/seanson [@hackebrot]: https://github.com/hackebrot [@ssd71]: https://github.com/ssd71 +[@jupe]: https://github.com/jupe diff --git a/README.md b/README.md index 88ac354..2912b98 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ $ pytest --md report.md emojis in the generated Markdown test report: ```text -$ pytest --emoji -v --md report.md +$ pytest --emoji --md-verbose --md report.md ``` ```Markdown @@ -119,7 +119,49 @@ $ pytest --emoji -v --md report.md - 1 error 😡 ``` +## pytest-metadata + +**pytest-md** also integrates with [pytest-metadata], which allows us to include +metadata in the generated Markdown test report: + +```text +$ pytest --md-metadata --metadata key=value +``` + +````markdown +# Test Report + +*Report generated on 02-Jul-2020 at 14:11:48 by [pytest-md]* + +[pytest-md]: https://github.com/hackebrot/pytest-md + +## Summary + +8 tests ran in 0.08 seconds + +- 1 error +- 1 failed +- 3 passed +- 1 skipped +- 1 xfailed +- 1 xpassed + +## Metadata + +Python: 3.7.7 +Platform: Darwin-19.5.0-x86_64-i386-64bit +Packages + pytest: 5.4.3 + py: 1.9.0 + pluggy: 0.13.1 +key: value +Plugins + metadata: 1.10.0 + +```` + [pytest-emoji]: https://github.com/hackebrot/pytest-emoji +[pytest-metadata]: https://github.com/pytest-dev/pytest-metadata ## Credits diff --git a/pytest.ini b/pytest.ini index 29d34ba..c76fb50 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,4 @@ [pytest] markers = emoji: tests which are skipped if pytest-emoji is not installed. + metadata: tests which are skipped if pytest-metadata is not installed. diff --git a/src/pytest_md/plugin.py b/src/pytest_md/plugin.py index 46d95dd..8af7a1e 100644 --- a/src/pytest_md/plugin.py +++ b/src/pytest_md/plugin.py @@ -20,11 +20,14 @@ class Outcome(enum.Enum): class MarkdownPlugin: """Plugin for generating Markdown reports.""" - def __init__(self, config, report_path, emojis_enabled: bool = False) -> None: + def __init__(self, config, report_path, + emojis_enabled: bool = False, + metadata_enabled: bool = False) -> None: self.config = config self.report_path = report_path self.report = "" self.emojis_enabled = emojis_enabled + self.metadata_enabled = metadata_enabled self.reports: Dict[Outcome, List] = collections.defaultdict(list) @@ -37,7 +40,7 @@ def _retrieve_emojis(self): def emoji(short, verbose): """Return the short or verbose emoji based on self.config.""" - if self.config.option.verbose > 0: + if self.config.option.md_verbose > 0: return verbose return short @@ -151,6 +154,25 @@ def create_summary(self) -> str: return summary + "\n\n" + outcome_text + def create_metadata(self) -> str: + """ Create environment section for the Markdown report.""" + outcome_text = "" + + def _generate_md(items, indentation=0): + nonlocal outcome_text + for key, value in items: + if isinstance(value, dict): + outcome_text += f'{" "*indentation}* {key}\n' + _generate_md(value.items(), indentation+2) + else: + outcome_text += f'{" "*indentation}* {key}: {value}\n' + + _generate_md(self.config._metadata.items()) + + summary = "## Metadata" + + return summary + "\n\n" + outcome_text + def create_results(self) -> str: """Create results for the individual tests for the Markdown report.""" @@ -217,7 +239,11 @@ def pytest_sessionfinish(self, session) -> None: self.report += f"{project_link}\n" self.report += f"{summary}\n" - if self.config.option.verbose > 0: + if self.metadata_enabled: + metadata = self.create_metadata() + self.report += f"{metadata}" + + if self.config.option.md_verbose: results = self.create_results() self.report += f"{results}" @@ -236,6 +262,18 @@ def pytest_addoption(parser): default=None, help="create markdown report file at given path.", ) + group.addoption( + "--md-metadata", + action="store_true", + dest="md_metadata", + help="Add environment section to report", + ) + group.addoption( + "--md-verbose", + action="store_true", + dest="md_verbose", + help="individual test report", + ) def pytest_configure(config) -> None: @@ -254,10 +292,17 @@ def emojis_enabled() -> bool: return config.option.emoji is True + def metadata_enabled() -> bool: + """ Check if pytest-metadata is installed and enabled """ + if not config.pluginmanager.hasplugin('metadata'): + return False + return config.option.md_metadata + config._md = MarkdownPlugin( config, report_path=pathlib.Path(mdpath).expanduser().resolve(), emojis_enabled=emojis_enabled(), + metadata_enabled=metadata_enabled() ) config.pluginmanager.register(config._md, "md_plugin") diff --git a/tests/conftest.py b/tests/conftest.py index 16b9e41..89be22b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ import enum import datetime import textwrap +import re import freezegun import pytest @@ -107,6 +108,8 @@ class Mode(enum.Enum): NORMAL = "normal" VERBOSE = "verbose" + METADATA = "metadata" + EMOJI_METADATA = "emoji_metadata" EMOJI_NORMAL = "emoji_normal" EMOJI_VERBOSE = "emoji_verbose" @@ -116,9 +119,11 @@ def fixture_cli_options(mode): """Return CLI options for the different test scenarios.""" cli_options = { Mode.NORMAL: [], - Mode.VERBOSE: ["--verbose"], + Mode.VERBOSE: ["--md-verbose"], + Mode.METADATA: ["--md-metadata", "--metadata", "key", "value"], + Mode.EMOJI_METADATA: ["--emoji", "--md-metadata", "--metadata", "key", "value"], Mode.EMOJI_NORMAL: ["--emoji"], - Mode.EMOJI_VERBOSE: ["--verbose", "--emoji"], + Mode.EMOJI_VERBOSE: ["--md-verbose", "--emoji"], } return cli_options[mode] @@ -242,6 +247,62 @@ def test_failed(): """ ) + if mode is Mode.METADATA: + + return re.compile(textwrap.dedent( + f"""\ + \\# Test Report + + \\*Report generated on {report_date} at {report_time} by \\[pytest-md\\]\\* + + \\[pytest-md\\]\\: https://github\\.com/hackebrot/pytest-md + + \\#\\# Summary + + 8 tests ran in 0.00 seconds + + - 1 error + - 1 failed + - 3 passed + - 1 skipped + - 1 xfailed + - 1 xpassed + + \\#\\# Metadata + + [\\w\\W]+ + * key\\: value + [\\w\\W]+ + """)) + + if mode is Mode.EMOJI_METADATA: + + return re.compile(textwrap.dedent( + f"""\ + \\# Test Report + + \\*Report generated on {report_date} at {report_time} by \\[pytest-md\\]\\* \\📝 + + \\[pytest-md\\]\\: https://github\\.com/hackebrot/pytest-md + + \\#\\# Summary + + 8 tests ran in 0.00 seconds \\⏱ + + - 1 \\💩 + - 1 \\😿 + - 3 \\🦊 + - 1 \\🙈 + - 1 \\🤓 + - 1 \\😜 + + \\#\\# Metadata + + [\\w\\W]+ + * key\\: value + [\\w\\W]+ + """)) + # Return the default report for Mode.NORMAL and Mode.VERBOSE if mode is Mode.VERBOSE: return textwrap.dedent( @@ -349,7 +410,7 @@ def test_failed(): @pytest.fixture(name="report_path") def fixture_report_path(tmp_path): """Return a temporary path for writing the Markdown report.""" - return tmp_path / "emoji_report.md" + return tmp_path / "report.md" def pytest_make_parametrize_id(config, val): @@ -371,17 +432,26 @@ def pytest_generate_tests(metafunc): [ Mode.NORMAL, Mode.VERBOSE, + pytest.param(Mode.METADATA, marks=pytest.mark.metadata), pytest.param(Mode.EMOJI_NORMAL, marks=pytest.mark.emoji), pytest.param(Mode.EMOJI_VERBOSE, marks=pytest.mark.emoji), + pytest.param(Mode.EMOJI_METADATA, marks=[pytest.mark.emoji,pytest.mark.metadata]), ], ) def pytest_collection_modifyitems(items, config): - """Skip tests marked with "emoji" if pytest-emoji is not installed.""" - if config.pluginmanager.hasplugin("emoji"): + """ + Skip tests marked with "emoji" if pytest-emoji and + pytest-metadata is not installed. + """ + has_emoji = config.pluginmanager.hasplugin("emoji") + has_metadata = config.pluginmanager.hasplugin("metadata") + if has_emoji and has_metadata: return for item in items: - if item.get_closest_marker("emoji"): + if item.get_closest_marker("emoji") and not has_emoji: item.add_marker(pytest.mark.skip(reason="pytest-emoji is not installed")) + if item.get_closest_marker("metadata") and not has_metadata: + item.add_marker(pytest.mark.skip(reason="pytest-metadata is not installed")) diff --git a/tests/test_generate_report.py b/tests/test_generate_report.py index e797061..d5cba20 100644 --- a/tests/test_generate_report.py +++ b/tests/test_generate_report.py @@ -1,3 +1,6 @@ +from typing import Pattern + + def test_generate_report(testdir, cli_options, report_path, report_content): """Check the contents of a generated Markdown report.""" # run pytest with the following CLI options @@ -7,5 +10,10 @@ def test_generate_report(testdir, cli_options, report_path, report_content): # as we have at least one failure assert result.ret == 1 + report = report_path.read_text() + # Check the generated Markdown report - assert report_path.read_text() == report_content + if isinstance(report_content, Pattern): + assert report_content.match(report) + else: + assert report == report_content diff --git a/tox.ini b/tox.ini index 187d873..4458d44 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,12 @@ [tox] -envlist = py36,py37,{py36,py37}-emoji,mypy,flake8 +envlist = py36, py37, {py36, py37}-{emoji, metadata, emoji-metadata}, mypy, flake8 [testenv] deps = freezegun pytest>=5.4.0 emoji: pytest-emoji + metadata: pytest-metadata commands = pytest -v {posargs:tests} [testenv:flake8]