-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add TipsPlugin to provide helpful tips (#105)
- Loading branch information
Showing
6 changed files
with
229 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from argparse import Namespace | ||
from typing import Any | ||
|
||
import pytest | ||
|
||
from vedro.core import Dispatcher | ||
from vedro.events import ArgParsedEvent | ||
from vedro.plugins.tip_adviser import TipAdviser, TipAdviserPlugin | ||
|
||
__all__ = ("dispatcher", "tip_adviser", "fire_arg_parsed_event",) | ||
|
||
|
||
@pytest.fixture() | ||
def dispatcher() -> Dispatcher: | ||
return Dispatcher() | ||
|
||
|
||
@pytest.fixture() | ||
def tip_adviser(dispatcher: Dispatcher) -> TipAdviserPlugin: | ||
tip_adviser = TipAdviserPlugin(TipAdviser) | ||
tip_adviser.subscribe(dispatcher) | ||
return tip_adviser | ||
|
||
|
||
async def fire_arg_parsed_event(dispatcher: Dispatcher, **kwargs: Any) -> None: | ||
args = Namespace(**kwargs) | ||
await dispatcher.fire(ArgParsedEvent(args)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
from unittest.mock import patch | ||
|
||
import pytest | ||
from baby_steps import given, then, when | ||
|
||
from vedro.core import Dispatcher, Report | ||
from vedro.events import CleanupEvent | ||
from vedro.plugins.tip_adviser import TipAdviser, TipAdviserPlugin | ||
|
||
from ._utils import dispatcher, fire_arg_parsed_event, tip_adviser | ||
|
||
__all__ = ("dispatcher", "tip_adviser",) # fixtures | ||
|
||
|
||
@pytest.mark.usefixtures(tip_adviser.__name__) | ||
async def test_no_tips_with_default_args(*, dispatcher: Dispatcher): | ||
with given: | ||
await fire_arg_parsed_event(dispatcher) | ||
|
||
report = Report() | ||
cleanup_event = CleanupEvent(report) | ||
|
||
with when: | ||
await dispatcher.fire(cleanup_event) | ||
|
||
with then: | ||
assert report.summary == [] | ||
|
||
|
||
async def test_no_tips_when_plugin_disabled(*, dispatcher: Dispatcher): | ||
with given: | ||
class CustomTipAdviser(TipAdviser): | ||
show_tips = False | ||
|
||
tip_adviser = TipAdviserPlugin(CustomTipAdviser) | ||
tip_adviser.subscribe(dispatcher) | ||
|
||
await fire_arg_parsed_event(dispatcher, repeats=2) | ||
|
||
report = Report() | ||
cleanup_event = CleanupEvent(report) | ||
|
||
with when: | ||
await dispatcher.fire(cleanup_event) | ||
|
||
with then: | ||
assert report.summary == [] | ||
|
||
|
||
@pytest.mark.parametrize(("seq_idx", "tip"), [ | ||
(0, "Tip: Consider using `--fixed-seed` for consistent results across repeated runs"), | ||
(1, "Tip: To disable these tips, run `vedro plugin disable vedro.plugins.tip_adviser`"), | ||
]) | ||
@pytest.mark.usefixtures(tip_adviser.__name__) | ||
async def test_random_tip_selection_with_repeats(seq_idx: int, tip: str, *, | ||
dispatcher: Dispatcher): | ||
with given: | ||
await fire_arg_parsed_event(dispatcher, repeats=2) | ||
|
||
report = Report() | ||
cleanup_event = CleanupEvent(report) | ||
|
||
with when, patch("random.choice", lambda seq: seq[seq_idx]): | ||
await dispatcher.fire(cleanup_event) | ||
|
||
with then: | ||
assert report.summary == [tip] | ||
|
||
|
||
@pytest.mark.parametrize(("seq_idx", "tip"), [ | ||
(0, "Tip: Consider using `--fixed-seed` for consistent results across repeated runs"), | ||
(1, "Tip: Consider using `--fail-fast-on-repeat` to to stop after the first failing repeat"), | ||
(2, "Tip: To disable these tips, run `vedro plugin disable vedro.plugins.tip_adviser`"), | ||
]) | ||
@pytest.mark.usefixtures(tip_adviser.__name__) | ||
async def test_random_tip_selection_with_repeats_and_fail_fast(seq_idx: int, tip: str, *, | ||
dispatcher: Dispatcher): | ||
with given: | ||
await fire_arg_parsed_event(dispatcher, repeats=2, fail_fast=True) | ||
|
||
report = Report() | ||
cleanup_event = CleanupEvent(report) | ||
|
||
with when, patch("random.choice", lambda seq: seq[seq_idx]): | ||
await dispatcher.fire(cleanup_event) | ||
|
||
with then: | ||
assert report.summary == [tip] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from ._tip_adviser import TipAdviser, TipAdviserPlugin | ||
|
||
__all__ = ("TipAdviser", "TipAdviserPlugin",) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import random | ||
from typing import Type, final | ||
|
||
from vedro.core import Dispatcher, Plugin, PluginConfig | ||
from vedro.events import ArgParsedEvent, CleanupEvent | ||
|
||
__all__ = ("TipAdviser", "TipAdviserPlugin",) | ||
|
||
|
||
@final | ||
class TipAdviserPlugin(Plugin): | ||
""" | ||
Provides tips and suggestions based on command-line arguments after test execution. | ||
The `TipAdviserPlugin` analyzes specific command-line arguments and offers relevant tips | ||
to enhance the test execution experience. | ||
Tips are displayed in the summary report after the test run. | ||
""" | ||
|
||
def __init__(self, config: Type["TipAdviser"]) -> None: | ||
""" | ||
Initialize the TipAdviserPlugin with the provided configuration. | ||
:param config: The TipAdviser configuration class. | ||
""" | ||
super().__init__(config) | ||
self._show_tips: bool = config.show_tips | ||
self._repeats: int = 1 | ||
self._fail_fast: bool = False | ||
self._fixed_seed: bool = False | ||
|
||
def subscribe(self, dispatcher: Dispatcher) -> None: | ||
""" | ||
Subscribe to Vedro events for parsing arguments and cleaning up after execution. | ||
:param dispatcher: The dispatcher to register event listeners on. | ||
""" | ||
dispatcher.listen(ArgParsedEvent, self.on_arg_parsed) \ | ||
.listen(CleanupEvent, self.on_cleanup) | ||
|
||
def on_arg_parsed(self, event: ArgParsedEvent) -> None: | ||
""" | ||
Handle the event after command-line arguments are parsed. | ||
Extract relevant arguments and store their values for use in generating tips. | ||
:param event: The ArgParsedEvent instance containing parsed arguments. | ||
""" | ||
self._repeats = event.args.repeats if hasattr(event.args, 'repeats') else 1 | ||
self._fail_fast = event.args.fail_fast if hasattr(event.args, 'fail_fast') else False | ||
self._fixed_seed = event.args.fixed_seed if hasattr(event.args, 'fixed_seed') else False | ||
|
||
# Note: In Vedro, plugins are generally designed to operate independently and | ||
# do not have knowledge of each other's existence. This modular design ensures | ||
# that plugins can be used in isolation without unintended dependencies or | ||
# interference. | ||
# However, the `TipAdviserPlugin` is an experimental plugin that is aware of | ||
# certain other plugins and their associated command-line arguments. | ||
# This awareness allows it to provide helpful tips based on the presence and | ||
# configuration of those plugins. While this behavior breaks the typical | ||
# isolation of Vedro plugins, it is intentional to enhance the user experience | ||
# by offering actionable suggestions tailored to the test execution context. | ||
|
||
def on_cleanup(self, event: CleanupEvent) -> None: | ||
""" | ||
Handle the cleanup event by displaying a random tip, if applicable. | ||
Based on the parsed arguments, generates tips for enhancing test runs. Tips are | ||
added to the test run's summary report if the `show_tips` option is enabled. | ||
:param event: The CleanupEvent instance containing the test report. | ||
""" | ||
if not self._show_tips: | ||
return | ||
|
||
tips = [] | ||
if self._repeats > 1 and not self._fixed_seed: | ||
tips.append( | ||
"Consider using `--fixed-seed` for consistent results across repeated runs" | ||
) | ||
if self._repeats > 1 and self._fail_fast: | ||
tips.append( | ||
"Consider using `--fail-fast-on-repeat` to to stop after the first failing repeat" | ||
) | ||
|
||
if len(tips) > 0: | ||
tips.append( | ||
"To disable these tips, run `vedro plugin disable vedro.plugins.tip_adviser`" | ||
) | ||
random_tip = random.choice(tips) | ||
event.report.add_summary(f"Tip: {random_tip}") | ||
|
||
|
||
class TipAdviser(PluginConfig): | ||
""" | ||
Configuration class for the TipAdviserPlugin. | ||
Defines settings for the TipAdviserPlugin, including whether tips should be displayed | ||
during test execution. | ||
""" | ||
|
||
plugin = TipAdviserPlugin | ||
description = "Provides random tips based on Vedro command-line arguments" | ||
|
||
# If True, the plugin will display tips at the end of the test run | ||
show_tips: bool = True |