Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved hypothesis outputs #605

Merged
merged 4 commits into from
Jun 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion brownie/_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,6 @@ def _modify_hypothesis_settings(settings, name, parent):
name,
parent=hp_settings.get_profile(parent),
database=DirectoryBasedExampleDatabase(_get_data_folder().joinpath("hypothesis")),
report_multiple_bugs=False,
**settings,
)
hp_settings.load_profile(name)
Expand Down
1 change: 1 addition & 0 deletions brownie/data/default-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ console:
hypothesis:
deadline: null
max_examples: 50
report_multiple_bugs: False
stateful_step_count: 10

autofetch_sources: false
Expand Down
3 changes: 3 additions & 0 deletions brownie/test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ def isolation_wrapper(*args, **kwargs):
hy_given = _hypothesis_given(*given_args, **given_kwargs)
hy_wrapped = hy_given(test)

# modify the wrapper name so it shows correctly in test report
isolation_wrapper.__name__ = test.__name__

if hasattr(hy_wrapped, "hypothesis"):
hy_wrapped.hypothesis.inner_test = isolation_wrapper
return hy_wrapped
Expand Down
62 changes: 57 additions & 5 deletions brownie/test/managers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

import json
from hashlib import sha1
from pathlib import Path

from hypothesis.reporting import reporter as hy_reporter
from py.path import local

import brownie
from brownie._config import CONFIG
from brownie.project.scripts import _get_ast_hash
from brownie.test import _apply_given_wrapper, coverage, output
Expand Down Expand Up @@ -69,6 +72,15 @@ def __init__(self, config, project):
for txhash, coverage_eval in hashes["tx"].items():
coverage._add_cached_transaction(txhash, coverage_eval)

def _reduce_path_strings(self, text):
# convert absolute path strings to relative ones, prior to outputting to console
base_path = f"{Path(brownie.__file__).parent.as_posix()}"
project_path = f"{self.project_path.as_posix()}/"

text = text.replace(base_path, "brownie")
text = text.replace(project_path, "")
return text

def _path(self, path):
return self.project_path.joinpath(path).relative_to(self.project_path).as_posix()

Expand Down Expand Up @@ -104,15 +116,28 @@ def pytest_sessionstart(self):
Called after the `Session` object has been created and before performing
collection and entering the run test loop.

Removes `PytestAssertRewriteWarning` warnings from the terminalreporter.
This prevents warnings that "the `brownie` library was already imported and
so related assertions cannot be rewritten". The warning is not relevant
for end users who are performing tests with brownie, not on brownie,
so we suppress it to avoid confusion.
* Replaces the default hypothesis reporter with a one that applies source
highlights and increased vertical space between results. The effect is
seen in output for `hypothesis.errors.MultipleFailures`.

* Removes `PytestAssertRewriteWarning` warnings from the terminalreporter.
This prevents warnings that "the `brownie` library was already imported and
so related assertions cannot be rewritten". The warning is not relevant
for end users who are performing tests with brownie, not on brownie,
so we suppress it to avoid confusion.

Removal of pytest warnings must be handled in this hook because session
information is passed between xdist workers and master prior to test execution.
"""

def _hypothesis_reporter(text):
text = self._reduce_path_strings(text)
if text.startswith("Falsifying example") or text.startswith("Traceback"):
reporter.write("\n")
reporter._tw._write_source(text.split("\n"))

hy_reporter.default = _hypothesis_reporter

reporter = self.config.pluginmanager.get_plugin("terminalreporter")
warnings = reporter.stats.pop("warnings", [])
warnings = [i for i in warnings if "PytestAssertRewriteWarning" not in i.message]
Expand Down Expand Up @@ -152,6 +177,33 @@ def pytest_report_teststatus(self, report):
return "xpassed", "X", "XPASS"
return report.outcome, convert_outcome(report.outcome), report.outcome.upper()

def pytest_runtest_makereport(self, item):
"""
Return a _pytest.runner.TestReport object for the given pytest.Item and
_pytest.runner.CallInfo.

Applies source highlighting to hypothesis output that is not related to
`hypothesis.errors.MultipleFailures`.

Attributes
----------
item : pytest.Item
Object representing the currently active test
"""
if not hasattr(item, "hypothesis_report_information"):
return

reporter = self.config.pluginmanager.get_plugin("terminalreporter")

report = [x for i in item.hypothesis_report_information for x in i.split("\n")]
report = [self._reduce_path_strings(i) for i in report]
report = [
reporter._tw._highlight(i).rstrip("\n") if not i.lstrip().startswith("\x1b") else i
for i in report
]

item.hypothesis_report_information = report

def pytest_terminal_summary(self, terminalreporter):
"""
Add a section to terminal summary reporting.
Expand Down
3 changes: 2 additions & 1 deletion brownie/test/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,15 @@ def pytest_configure(config):
print(f"{color.format_tb(e)}\n")
raise pytest.UsageError("Unable to load project")

# apply __tracebackhide__ so brownie internals aren't included in tracebacks
# do not include brownie internals in tracebacks
base_path = Path(sys.modules["brownie"].__file__).parent.as_posix()
for module in [
v
for v in sys.modules.values()
if getattr(v, "__file__", None) and v.__file__.startswith(base_path)
]:
module.__tracebackhide__ = True
module.__hypothesistracebackhide__ = True

# enable verbose output if stdout capture is disabled
if config.getoption("capture") == "no":
Expand Down
3 changes: 2 additions & 1 deletion docs/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ Default settings for :ref:`property-based<hypothesis>` and :ref:`stateful<hypoth
hypothesis:
deadline: null
max_examples: 50
report_multiple_bugs: False
stateful_step_count: 10

Other Settings
Expand Down Expand Up @@ -249,4 +250,4 @@ Other Settings

This is useful if another application, such as a front end framework, needs access to deployment artifacts while you are on a development network.

default value: ``false``
default value: ``false``
6 changes: 6 additions & 0 deletions docs/tests-hypothesis-property.rst
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,12 @@ Available Settings

default-value: ``50``

.. py:attributes:: report_multiple_bugs

Because Hypothesis runs each test many times, it can sometimes find multiple bugs in a single run. Reporting all of them at once can be useful, but also produces significantly longer and less descriptive output when compared to reporting a single error.

default-value: ``False``

.. py:attribute:: stateful_step_count

The maximum number of rules to execute in a stateful program before ending the run and considering it to have passed.
Expand Down