Skip to content

Commit 0b3babf

Browse files
authored
Merge pull request #1681 from Bastian-Krause/bst/strategy-errors
Introduce Broken Strategy State, Prevent Further Use and Skip Subsequent Tests
2 parents 0ba0f75 + f404b5c commit 0b3babf

File tree

15 files changed

+98
-13
lines changed

15 files changed

+98
-13
lines changed

doc/development.rst

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ Start by creating a strategy skeleton:
226226
import attr
227227

228228
from labgrid.step import step
229-
from labgrid.strategy import Strategy, StrategyError
229+
from labgrid.strategy import Strategy, StrategyError, never_retry
230230
from labgrid.factory import target_factory
231231

232232
class Status(enum.Enum):
@@ -239,6 +239,7 @@ Start by creating a strategy skeleton:
239239

240240
status = attr.ib(default=Status.unknown)
241241

242+
@never_retry
242243
@step()
243244
def transition(self, status, *, step):
244245
if not isinstance(status, Status):
@@ -262,9 +263,21 @@ It is possible to reference drivers via their protocol, e.g.
262263
Note that drivers which implement multiple protocols must not be referenced
263264
multiple times via different protocols.
264265
The ``Status`` class needs to be extended to cover the states of your strategy,
265-
then for each state an ``elif`` entry in the transition function needs to be
266+
then for each state an ``elif`` entry in the ``transition()`` method needs to be
266267
added.
267268

269+
.. note::
270+
Since infrastructure failures or broken strategies typically cannot recover,
271+
it makes little sense to continue operating with such a strategy after an
272+
error has occurred.
273+
To clearly mark a strategy as unusable after failure (and to avoid cascading
274+
errors in subsequent calls) the strategy's ``transition()`` method (and
275+
optionally its ``force()`` method) can be decorated with the
276+
``@never_retry`` decorator.
277+
This decorator causes the strategy to store the encountered exception in its
278+
``broken`` attribute and raise a ``StrategyError`` for the original and all
279+
subsequent calls to the decorated methods.
280+
268281
Lets take a look at the builtin `BareboxStrategy`.
269282
The Status enum for the BareboxStrategy:
270283

doc/usage.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,10 @@ target (session scope)
421421

422422
strategy (session scope)
423423
Used to access the :any:`Strategy` configured in the 'main' :any:`Target`.
424+
If the Strategy enters broken state, all subsequent tests requesting it via
425+
this fixture will be skipped.
426+
See also :any:`never_retry` for an easy way to mark strategies broken on
427+
error.
424428

425429
Command-Line Options
426430
~~~~~~~~~~~~~~~~~~~~

examples/qemu-networking/qemunetworkstrategy.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import attr
1818

1919
from labgrid import target_factory, step
20-
from labgrid.strategy import Strategy, StrategyError
20+
from labgrid.strategy import Strategy, StrategyError, never_retry
2121
from labgrid.util import get_free_port
2222

2323

@@ -77,6 +77,7 @@ def update_network_service(self):
7777
networkservice.address = new_address
7878
networkservice.port = self.__remote_port
7979

80+
@never_retry
8081
@step(args=["state"])
8182
def transition(self, state, *, step):
8283
if not isinstance(state, Status):

examples/strategy/bareboxrebootstrategy.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from labgrid import target_factory, step
66
from labgrid.driver import BareboxDriver, ShellDriver
77
from labgrid.protocol import PowerProtocol
8-
from labgrid.strategy import Strategy
8+
from labgrid.strategy import Strategy, never_retry
99

1010

1111
@attr.s(eq=False)
@@ -58,6 +58,7 @@ class BareboxRebootStrategy(Strategy):
5858
def __attrs_post_init__(self):
5959
super().__attrs_post_init__()
6060

61+
@never_retry
6162
@step(args=["new_status"])
6263
def transition(self, new_status, *, step):
6364
if not isinstance(new_status, Status):

examples/strategy/quartusstrategy.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from labgrid import target_factory, step
66
from labgrid.driver import QuartusHPSDriver, SerialDriver
77
from labgrid.protocol import PowerProtocol
8-
from labgrid.strategy import Strategy
8+
from labgrid.strategy import Strategy, never_retry
99

1010

1111
@attr.s(eq=False)
@@ -37,6 +37,7 @@ class QuartusHPSStrategy(Strategy):
3737
def __attrs_post_init__(self):
3838
super().__attrs_post_init__()
3939

40+
@never_retry
4041
@step(args=["status"])
4142
def transition(self, status, *, step):
4243
if not isinstance(status, Status):

examples/usbpower/examplestrategy.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from labgrid.driver import BareboxDriver, ShellDriver, USBSDMuxDriver
66
from labgrid import step, target_factory
77
from labgrid.protocol import PowerProtocol
8-
from labgrid.strategy import Strategy
8+
from labgrid.strategy import Strategy, never_retry
99

1010

1111
@attr.s(eq=False)
@@ -36,6 +36,7 @@ class ExampleStrategy(Strategy):
3636
def __attrs_post_init__(self):
3737
super().__attrs_post_init__()
3838

39+
@never_retry
3940
@step(args=["status"])
4041
def transition(self, status, *, step):
4142
if not isinstance(status, Status):

labgrid/pytestplugin/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
from .fixtures import pytest_addoption, env, target, strategy
2-
from .hooks import pytest_configure, pytest_collection_modifyitems, pytest_cmdline_main
2+
from .hooks import pytest_configure, pytest_collection_modifyitems, pytest_cmdline_main, pytest_runtest_setup

labgrid/pytestplugin/hooks.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from ..consoleloggingreporter import ConsoleLoggingReporter
77
from ..util.helper import processwrapper
88
from ..logging import StepFormatter, StepLogger
9+
from ..exceptions import NoStrategyFoundError
910

1011
LABGRID_ENV_KEY = pytest.StashKey[Environment]()
1112

@@ -121,3 +122,23 @@ def pytest_collection_modifyitems(config, items):
121122
reason=f'Skipping because features "{missing_feature}" are not supported'
122123
)
123124
item.add_marker(skip)
125+
126+
@pytest.hookimpl(tryfirst=True)
127+
def pytest_runtest_setup(item):
128+
"""
129+
Skip test if one of the targets uses a strategy considered broken.
130+
"""
131+
# Before any fixtures run for the test, check if the session-scoped strategy fixture was
132+
# requested (might have been executed already for a prior test). If that's the case and the
133+
# strategy is broken, skip the test.
134+
if "strategy" in item.fixturenames:
135+
env = item.config.stash[LABGRID_ENV_KEY]
136+
# skip test even if only one of the targets in the env has a broken strategy
137+
for target_name in env.config.get_targets():
138+
target = env.get_target(target_name)
139+
try:
140+
strategy = target.get_strategy()
141+
if strategy.broken:
142+
pytest.skip(f"{strategy.__class__.__name__} is in broken state")
143+
except NoStrategyFoundError:
144+
pass

labgrid/strategy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from .common import Strategy, StrategyError
1+
from .common import Strategy, StrategyError, never_retry
22
from .bareboxstrategy import BareboxStrategy
33
from .shellstrategy import ShellStrategy
44
from .ubootstrategy import UBootStrategy

labgrid/strategy/bareboxstrategy.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from ..factory import target_factory
66
from ..step import step
7-
from .common import Strategy, StrategyError
7+
from .common import Strategy, StrategyError, never_retry
88

99

1010
class Status(enum.Enum):
@@ -30,6 +30,7 @@ class BareboxStrategy(Strategy):
3030
def __attrs_post_init__(self):
3131
super().__attrs_post_init__()
3232

33+
@never_retry
3334
@step(args=['status'])
3435
def transition(self, status, *, step): # pylint: disable=redefined-outer-name
3536
if not isinstance(status, Status):
@@ -63,6 +64,7 @@ def transition(self, status, *, step): # pylint: disable=redefined-outer-name
6364
)
6465
self.status = status
6566

67+
@never_retry
6668
@step(args=['status'])
6769
def force(self, status):
6870
if not isinstance(status, Status):

0 commit comments

Comments
 (0)