Skip to content

Commit

Permalink
show skip reason (#54)
Browse files Browse the repository at this point in the history
  • Loading branch information
tsv1 authored Jun 29, 2023
1 parent 0656bc5 commit 5b2ea93
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 34 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## v1.9

### v1.9.1 (2023-06-13)

- [RichReporter] Add `scope_width` param [#49](https://github.com/tsv1/vedro/pull/49)

### v1.9.0 (2023-05-14)

- [RichReporter] Humanize elapsed time [#45](https://github.com/tsv1/vedro/pull/45)
Expand Down
15 changes: 15 additions & 0 deletions tests/core/test_virtual_scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,21 @@ def test_virtual_scenario_skip(*, scenario_: Type[Scenario]):
with then:
assert res is None
assert virtual_scenario.is_skipped() is True
assert virtual_scenario.skip_reason is None


def test_virtual_scenario_skip_with_reason(*, scenario_: Type[Scenario]):
with given:
virtual_scenario = VirtualScenario(scenario_, [])
reason = "<reason>"

with when:
res = virtual_scenario.skip(reason)

with then:
assert res is None
assert virtual_scenario.is_skipped() is True
assert virtual_scenario.skip_reason == reason


def test_virtual_scenario_init(*, scenario_: Type[Scenario]):
Expand Down
103 changes: 100 additions & 3 deletions tests/plugins/director/rich/reporter/test_rich_reporter_skipped.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
fire_arg_parsed_event,
make_aggregated_result,
make_scenario_result,
make_vscenario,
printer_,
rich_reporter,
)
Expand All @@ -22,10 +23,9 @@

@pytest.mark.asyncio
@pytest.mark.usefixtures(rich_reporter.__name__)
@pytest.mark.parametrize("show_paths", [False, True])
async def test_scenario_skipped(show_paths: bool, *, dispatcher: Dispatcher, printer_: Mock):
async def test_scenario_skipped(*, dispatcher: Dispatcher, printer_: Mock):
with given:
await fire_arg_parsed_event(dispatcher, show_paths=show_paths)
await fire_arg_parsed_event(dispatcher)

scenario_result = make_scenario_result().mark_skipped()
aggregated_result = make_aggregated_result(scenario_result)
Expand All @@ -41,6 +41,103 @@ async def test_scenario_skipped(show_paths: bool, *, dispatcher: Dispatcher, pri
]


@pytest.mark.asyncio
@pytest.mark.usefixtures(rich_reporter.__name__)
async def test_scenario_skipped_with_reason(*, dispatcher: Dispatcher, printer_: Mock):
with given:
await fire_arg_parsed_event(dispatcher)

reason = "<reason>"
vscenario = make_vscenario()
vscenario.skip(reason)

scenario_result = make_scenario_result(vscenario).mark_skipped()
aggregated_result = make_aggregated_result(scenario_result)
event = ScenarioReportedEvent(aggregated_result)

with when:
await dispatcher.fire(event)

with then:
subject = aggregated_result.scenario.subject
assert printer_.mock_calls == [
call.print_scenario_subject(subject, ScenarioStatus.SKIPPED, elapsed=None, prefix=" "),
call.print_scenario_caption(f"> {reason}", prefix=" " * 3)
]


@pytest.mark.asyncio
async def test_scenario_skipped_with_reason_disabled(*, dispatcher: Dispatcher,
rich_reporter: RichReporterPlugin,
printer_: Mock):
with given:
rich_reporter._show_skip_reason = False
await fire_arg_parsed_event(dispatcher)

vscenario = make_vscenario()
vscenario.skip("<reason>")

scenario_result = make_scenario_result(vscenario).mark_skipped()
aggregated_result = make_aggregated_result(scenario_result)
event = ScenarioReportedEvent(aggregated_result)

with when:
await dispatcher.fire(event)

with then:
subject = aggregated_result.scenario.subject
assert printer_.mock_calls == [
call.print_scenario_subject(subject, ScenarioStatus.SKIPPED, elapsed=None, prefix=" ")
]


@pytest.mark.asyncio
@pytest.mark.usefixtures(rich_reporter.__name__)
async def test_scenario_skipped_show_paths(dispatcher: Dispatcher, printer_: Mock):
with given:
await fire_arg_parsed_event(dispatcher, show_paths=True)

scenario_result = make_scenario_result().mark_skipped()
aggregated_result = make_aggregated_result(scenario_result)
event = ScenarioReportedEvent(aggregated_result)

with when:
await dispatcher.fire(event)

with then:
subject = aggregated_result.scenario.subject
assert printer_.mock_calls == [
call.print_scenario_subject(subject, ScenarioStatus.SKIPPED, elapsed=None, prefix=" "),
call.print_scenario_caption(f"> {scenario_result.scenario.path.name}", prefix=" " * 3)
]


@pytest.mark.asyncio
@pytest.mark.usefixtures(rich_reporter.__name__)
async def test_scenario_skipped_with_reason_and_paths(*, dispatcher: Dispatcher, printer_: Mock):
with given:
await fire_arg_parsed_event(dispatcher, show_paths=True)

reason = "<reason>"
vscenario = make_vscenario()
vscenario.skip(reason)

scenario_result = make_scenario_result(vscenario).mark_skipped()
aggregated_result = make_aggregated_result(scenario_result)
event = ScenarioReportedEvent(aggregated_result)

with when:
await dispatcher.fire(event)

with then:
subject = aggregated_result.scenario.subject
assert printer_.mock_calls == [
call.print_scenario_subject(subject, ScenarioStatus.SKIPPED, elapsed=None, prefix=" "),
call.print_scenario_caption(f"> {reason}", prefix=" " * 3),
call.print_scenario_caption(f"> {scenario_result.scenario.path.name}", prefix=" " * 3),
]


@pytest.mark.asyncio
@pytest.mark.usefixtures(rich_reporter.__name__)
async def test_scenario_skipped_show_timings(*, dispatcher: Dispatcher, printer_: Mock):
Expand Down
11 changes: 9 additions & 2 deletions vedro/core/_virtual_scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from hashlib import blake2b
from inspect import BoundArguments
from pathlib import Path
from typing import Any, List, Type, Union, cast
from typing import Any, List, Optional, Type, Union, cast

from .._scenario import Scenario
from ._virtual_step import VirtualStep
Expand All @@ -21,6 +21,7 @@ def __init__(self, orig_scenario: Type[Scenario], steps: List[VirtualStep]) -> N
self._steps = steps
self._path = Path(getattr(orig_scenario, "__file__", "."))
self._is_skipped = False
self._skip_reason: Union[str, None] = None

@property
def steps(self) -> List[VirtualStep]:
Expand Down Expand Up @@ -89,8 +90,14 @@ def namespace(self) -> str:
rel_path = os.path.relpath(self._path, module.split(".")[0])
return os.path.dirname(rel_path)

def skip(self) -> None:
def skip(self, reason: Optional[str] = None) -> None:
self._is_skipped = True
if reason:
self._skip_reason = reason

@property
def skip_reason(self) -> Optional[str]:
return self._skip_reason

def is_skipped(self) -> bool:
return self._is_skipped
Expand Down
38 changes: 24 additions & 14 deletions vedro/plugins/director/rich/_rich_reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def __init__(self, config: Type["RichReporter"], *,
self._scope_width = config.scope_width
self._tb_max_frames = config.tb_max_frames
self._show_skipped = config.show_skipped
self._show_skip_reason = config.show_skip_reason
self._show_timings = config.show_timings
self._show_paths = config.show_paths
self._show_steps = config.show_steps
Expand Down Expand Up @@ -118,23 +119,14 @@ def on_startup(self, event: StartupEvent) -> None:
self._printer.print_header()

def on_scenario_run(self, event: ScenarioRunEvent) -> None:
namespace = event.scenario_result.scenario.namespace
if namespace != self._namespace:
self._namespace = namespace
if self._hide_namespaces is False:
self._printer.print_namespace(namespace)

self._print_namespace(event.scenario_result.scenario.namespace)
if self._show_scenario_spinner:
self._printer.show_spinner(f" {event.scenario_result.scenario.subject}")

def on_scenario_skipped(self, event: ScenarioSkippedEvent) -> None:
if not self._show_skipped:
return
namespace = event.scenario_result.scenario.namespace
if namespace != self._namespace:
self._namespace = namespace
if self._hide_namespaces is False:
self._printer.print_namespace(namespace)
self._print_namespace(event.scenario_result.scenario.namespace)

def _print_exception(self, exc_info: ExcInfo) -> None:
if self._tb_pretty:
Expand All @@ -151,6 +143,12 @@ def _prefix_to_indent(self, prefix: str, indent: int = 0) -> str:
last_line = prefix.split("\n")[-1]
return (len(last_line) + indent) * " "

def _print_namespace(self, namespace: str) -> None:
if namespace != self._namespace:
self._namespace = namespace
if self._hide_namespaces is False:
self._printer.print_namespace(namespace)

def _print_scenario_passed(self, scenario_result: ScenarioResult, *, prefix: str = "") -> None:
elapsed = scenario_result.elapsed if self._show_timings else None
self._printer.print_scenario_subject(scenario_result.scenario.subject,
Expand All @@ -165,9 +163,7 @@ def _print_scenario_passed(self, scenario_result: ScenarioResult, *, prefix: str
elapsed=elapsed, prefix=step_prefix)

if self._show_paths:
caption = f"> {scenario_result.scenario.rel_path}"
caption_prefix = self._prefix_to_indent(prefix, indent=2)
self._printer.print_scenario_caption(caption, prefix=caption_prefix)
self._print_scenario_caption(f"{scenario_result.scenario.rel_path}", prefix=prefix)

def _print_scenario_failed(self, scenario_result: ScenarioResult, *, prefix: str = "") -> None:
elapsed = scenario_result.elapsed if self._show_timings else None
Expand Down Expand Up @@ -202,6 +198,17 @@ def _print_scenario_skipped(self, scenario_result: ScenarioResult, *,
elapsed=elapsed,
prefix=prefix)

skip_reason = scenario_result.scenario.skip_reason
if self._show_skip_reason and skip_reason:
self._print_scenario_caption(f"{skip_reason}", prefix=prefix)

if self._show_paths:
self._print_scenario_caption(f"{scenario_result.scenario.rel_path}", prefix=prefix)

def _print_scenario_caption(self, caption: str, *, prefix: str = "") -> None:
caption_prefix = self._prefix_to_indent(prefix, indent=2)
self._printer.print_scenario_caption(f"> {caption}", prefix=caption_prefix)

def _print_scenario_result(self, scenario_result: ScenarioResult, *, prefix: str = "") -> None:
if scenario_result.is_passed():
self._print_scenario_passed(scenario_result, prefix=prefix)
Expand Down Expand Up @@ -253,6 +260,9 @@ class RichReporter(PluginConfig):
# Show skipped scenarios
show_skipped: bool = True

# Show reason of skipped scenarios
show_skip_reason: bool = True

# Show the elapsed time of each scenario
show_timings: bool = False

Expand Down
4 changes: 2 additions & 2 deletions vedro/plugins/skipper/_only.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@


@overload
def only(scenario_or_reason: T) -> T: # pragma: no cover
def only(scenario_or_nothing: T) -> T: # pragma: no cover
pass


@overload
def only(scenario_or_reason: None = None) -> Callable[[T], T]: # pragma: no cover
def only(scenario_or_nothing: None = None) -> Callable[[T], T]: # pragma: no cover
pass


Expand Down
18 changes: 9 additions & 9 deletions vedro/plugins/skipper/_skip.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,34 @@


@overload
def skip(scenario_or_reason: T) -> T: # pragma: no cover
def skip(reason: T) -> T: # pragma: no cover
pass


@overload
def skip(scenario_or_reason: str) -> Callable[[T], T]: # pragma: no cover
def skip(reason: str) -> Callable[[T], T]: # pragma: no cover
pass


@overload
def skip(scenario_or_reason: None = None) -> Callable[[T], T]: # pragma: no cover
def skip(reason: None = None) -> Callable[[T], T]: # pragma: no cover
pass


def skip(scenario_or_reason=None): # type: ignore
def skip(reason=None): # type: ignore
def wrapped(scenario: T) -> T:
if not issubclass(scenario, Scenario):
raise TypeError("Decorator @skip can be used only with 'vedro.Scenario' subclasses")

setattr(scenario, "__vedro__skipped__", True)
if isinstance(scenario_or_reason, str):
setattr(scenario, "__vedro__skip_reason__", scenario_or_reason)
if isinstance(reason, str):
setattr(scenario, "__vedro__skip_reason__", reason)

return scenario

if (scenario_or_reason is None) or isinstance(scenario_or_reason, str):
if (reason is None) or isinstance(reason, str):
return wrapped
elif isclass(scenario_or_reason):
return wrapped(scenario_or_reason)
elif isclass(reason):
return wrapped(reason)
else:
raise TypeError('Usage: @skip or @skip("reason")')
14 changes: 10 additions & 4 deletions vedro/plugins/skipper/_skipper.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os
from typing import List, Optional, Type, Union
from typing import Any, List, Optional, Type, Union, cast

from vedro.core import Dispatcher, Plugin, PluginConfig, VirtualScenario
from vedro.events import ArgParsedEvent, ArgParseEvent, StartupEvent
Expand Down Expand Up @@ -67,9 +67,11 @@ def _normalize_path(self, file_or_dir: str) -> str:
path = os.path.join("scenarios", path)
return os.path.abspath(path)

def _get_scenario_attr(self, scenario: VirtualScenario, attr: str) -> bool:
def _get_scenario_attr(self, scenario: VirtualScenario, name: str, default_value: Any) -> Any:
template = getattr(scenario._orig_scenario, "__vedro__template__", None)
return getattr(template or scenario._orig_scenario, attr, False)
if template and hasattr(template, name):
return getattr(template, name)
return getattr(scenario._orig_scenario, name, default_value)

def _is_scenario_skipped(self, scenario: VirtualScenario) -> bool:
attr_name = "__vedro__skipped__"
Expand Down Expand Up @@ -121,6 +123,10 @@ def _is_scenario_ignored(self, scenario: VirtualScenario) -> bool:

return False

def _get_skip_reason(self, scenario: VirtualScenario) -> Union[str, None]:
skip_reason = self._get_scenario_attr(scenario, "__vedro__skip_reason__", None)
return cast(Union[str, None], skip_reason)

async def on_startup(self, event: StartupEvent) -> None:
special_scenarios = set()

Expand All @@ -130,7 +136,7 @@ async def on_startup(self, event: StartupEvent) -> None:
scheduler.ignore(scenario)
else:
if self._is_scenario_skipped(scenario):
scenario.skip()
scenario.skip(reason=self._get_skip_reason(scenario))
if self._is_scenario_special(scenario):
special_scenarios.add(scenario.unique_id)

Expand Down

0 comments on commit 5b2ea93

Please sign in to comment.