Skip to content
Open
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ venv37
.vscode/settings.json
docs/_build/
.DS_Store
.env
.env
*.lock
17 changes: 12 additions & 5 deletions src/fixate/core/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import ctypes
import logging
import warnings
from enum import Enum, unique
from functools import wraps
from collections import namedtuple
from fixate.core.exceptions import ParameterError, InvalidScalarQuantityError
Expand Down Expand Up @@ -407,16 +408,22 @@ class TestClass:
as the name of the test with the remaining lines stored as self.test_desc_long which will show in the test logs
"""

RT_ABORT = 1 # Abort the whole test sequence
RT_RETRY = 2 # Automatically retry up to "attempts"
RT_PROMPT = 3 # Prompt the user; Options are Abort the sequence, retry, or fail and continue
RT_FAIL = 4 # Automatically fail and move on
@unique
class RetryMode(Enum):
"""
Enum class for the Retry Modes.
"""

RT_ABORT = 1 # Abort the whole test sequence
RT_RETRY = 2 # Automatically retry up to "attempts"
RT_PROMPT = 3 # Prompt the user; Options are Abort the sequence, retry, or fail and continue
RT_FAIL = 4 # Automatically fail and move on

test_desc = None
test_desc_long = None
attempts = 1
tests = []
retry_type = RT_PROMPT
retry_type: RetryMode = RetryMode.RT_PROMPT
retry_exceptions = [BaseException] # Depreciated
skip_exceptions = []
abort_exceptions = [KeyboardInterrupt, AttributeError, NameError]
Expand Down
25 changes: 22 additions & 3 deletions src/fixate/sequencer.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,9 @@ def run_once(self):
if self.run_test():
top.index += 1
else:
if not self.retry_prompt():
if not self.retry_prompt(
retry_type=top.current().retry_type
):
# mark the test as failed and continue. else will loop and try again
self.tests_failed += 1
top.index += 1
Expand Down Expand Up @@ -371,7 +373,7 @@ def run_test(self):
)
attempts = 0
active_test_status = "ERROR"
if not self.retry_prompt():
if not self.retry_prompt(retry_type=active_test.retry_type):
self.tests_errored += 1
break
# Retry logic for exceptions
Expand All @@ -395,15 +397,32 @@ def run_test(self):
)
return active_test_status == "PASS"

def retry_prompt(self):
def retry_prompt(self, *, retry_type: TestClass.RetryMode):
"""Prompt the user when something goes wrong.

For retry return True, to fail return False and to abort raise and abort exception. Respect the
non_interactive flag, which can be set by the command line option --non-interactive
"""
if not isinstance(retry_type, TestClass.RetryMode):
raise TypeError(
f"Invalid retry_type:{retry_type} retry_type must be valid{type(retry_type).__name__}"
)

if self.non_interactive:
return False
if retry_type == TestClass.RetryMode.RT_PROMPT:
# RT_PROMPT is the default behaviour and will prompt the user below.
pass
if retry_type == TestClass.RetryMode.RT_RETRY:
# RT_RETRY behaviour is determined by active_test.attempt,
# By default it is set to 1. For example if the value is set to 3 it will
# retry the test 3 times if all fails then it prompts the user.
pass
if retry_type == TestClass.RetryMode.RT_FAIL:
return False
if retry_type == TestClass.RetryMode.RT_ABORT:
raise SequenceAbort("Sequence Aborted By The Test")

resp = user_retry_abort_fail(msg="")
if resp == "ABORT":
raise SequenceAbort("Sequence Aborted By User")
Expand Down
175 changes: 175 additions & 0 deletions test/test_retry_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
"""
Testing the RetryMode implementation.
"""

from fixate.core.checks import chk_true
from fixate.core.common import TestClass, TestList
from fixate.core.ui import user_info, user_ok


class SimpleTestToPass(TestClass):
"""Simple test case that always passes"""

test_desc = __qualname__

def set_up(self):
user_info("Setting up to pass")

def test(self):
user_info("Testing to Pass the test")
chk_true(True, "Passed the test")

def tear_down(self):
user_info("Well done, you wouldn't fail anyway!")


class SimpleTestToFail(TestClass):
"""Simple test case that always fails"""

test_desc = __qualname__
attempts = 2
retry_type = TestClass.RetryMode.RT_PROMPT

def set_up(self):
user_info("Setting up to fail")

def test(self):
user_info("Testing to Fail the test")
chk_true(False, "Failed the test")

def tear_down(self):
user_info("Well, you wouldn't pass anyway!")


class RetryTestPass(TestClass):
"""Simple test case that always passes but setup to retry if fails to test passing cases."""

test_desc = __qualname__
retry_type = TestClass.RetryMode.RT_RETRY
attempts = 3

def set_up(self):
user_info("Setting up to pass")

def test(self):
user_info("Testing to Pass the test")
chk_true(True, "Passed the test")

def tear_down(self):
user_info("Well done, you wouldn't fail anyway!")


class RetryTestFail(TestClass):
"""Simple test case that always fails and retries for the number of
attempts and prompts the user after all the attempts have failed..
"""

test_desc = __qualname__

retry_type = TestClass.RetryMode.RT_RETRY
attempts = 3

def set_up(self):
user_info("Setting up to fail")

def test(self):
user_info("Testing to Fail the test")
chk_true(False, "Failed the test")

def tear_down(self):
user_info("Well, you wouldn't pass anyway!")


# RT_FAIL = 4 # Automatically fail and move on
class RetryTestPassRTFAIL(TestClass):
"""Simple test case that always passes but setup to retry if fails to test passing cases."""

test_desc = __qualname__

retry_type = TestClass.RetryMode.RT_FAIL
attempts = 3

def set_up(self):
user_info("Setting up to pass")

def test(self):
user_info("Testing to Pass the test")
chk_true(True, "Passed the test")

def tear_down(self):
user_info("Well done, you wouldn't fail anyway!")


class RetryTestFailRTFAIL(TestClass):
"""Simple test case that always fails and retries for the number of
attempts. Then automatically failes the test and moves onto the next one."""

test_desc = __qualname__

retry_type = TestClass.RetryMode.RT_FAIL
attempts = 3

def set_up(self):
user_info("Setting up to fail")

def test(self):
user_info("Testing to Fail the test")
chk_true(False, "Failed the test")

def tear_down(self):
user_info("Well, you wouldn't pass anyway!")


class TestListRTFAIL(TestList):
"""Test list to test the RT_FAIL within the test list"""

def enter(self):
user_info("Entering the test list")

def exit(self):
user_info("Leaving the test list")


# RT_ABORT = 1 # Abort the whole test sequence
class RetryTestFailRTABORT(TestClass):
"""Simple test case that always fails and retries the number of attempts when final attempt has failed
It aborts the all the test. It may be useful if there is critical test that may damage Device Under Test if fails.
"""

test_desc = __qualname__

retry_type = TestClass.RetryMode.RT_ABORT
attempts = 3

def set_up(self):
user_ok("Setting up to abort, Press Enter to proceed")

def test(self):
user_info("Testing to Fail the test")
chk_true(False, "Failed the test")

def tear_down(self):
user_info("ABORTING!")


TEST_SEQUENCE = [
SimpleTestToPass(),
SimpleTestToFail(),
RetryTestPass(),
RetryTestFail(),
RetryTestPassRTFAIL(),
RetryTestFailRTFAIL(),
TestListRTFAIL(
[
RetryTestPassRTFAIL(),
RetryTestFailRTFAIL(),
]
),
RetryTestFailRTABORT(),
]

if __name__ == "__main__":
import fixate

argv = ["-p", __file__, "--serial_number", "1234567890"]
fixate.run(__file__, argv)