From 4ec2bc44471863504a31a9a6feff5914092afd4f Mon Sep 17 00:00:00 2001 From: sarpkarayigit Date: Thu, 8 Oct 2026 21:35:47 +1100 Subject: [PATCH 1/2] implemented retry_type checks in the TestClass, RT_FAIL automatically fails test and moves after attempts. RT_ABORT, aborts the test after attemts. --- src/fixate/core/common.py | 17 ++-- src/fixate/sequencer.py | 25 +++++- test/test_retry_type.py | 175 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 209 insertions(+), 8 deletions(-) create mode 100644 test/test_retry_type.py diff --git a/src/fixate/core/common.py b/src/fixate/core/common.py index 4f030b18..a1e00baa 100644 --- a/src/fixate/core/common.py +++ b/src/fixate/core/common.py @@ -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 @@ -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] diff --git a/src/fixate/sequencer.py b/src/fixate/sequencer.py index 6da95597..1eacd781 100644 --- a/src/fixate/sequencer.py +++ b/src/fixate/sequencer.py @@ -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 @@ -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 @@ -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") diff --git a/test/test_retry_type.py b/test/test_retry_type.py new file mode 100644 index 00000000..c2844a63 --- /dev/null +++ b/test/test_retry_type.py @@ -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) From 6af8c22faae2679b1e8d57231ffa2d943ffc2c1d Mon Sep 17 00:00:00 2001 From: sarpkarayigit Date: Thu, 8 Oct 2026 21:51:28 +1100 Subject: [PATCH 2/2] *.lock added to gitignore to ignore uv.lock file. --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 67bd81bf..3f062ab6 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,5 @@ venv37 .vscode/settings.json docs/_build/ .DS_Store -.env \ No newline at end of file +.env +*.lock \ No newline at end of file