Skip to content

Issue #5383 - Feature: Added global execution cli timeout#5594

Open
hassineabd wants to merge 3 commits into
robotframework:masterfrom
hassineabd:feature/cli-timeout
Open

Issue #5383 - Feature: Added global execution cli timeout#5594
hassineabd wants to merge 3 commits into
robotframework:masterfrom
hassineabd:feature/cli-timeout

Conversation

@hassineabd
Copy link
Copy Markdown
Member

Issue #5383

Key Changes:

  • New CLI Option: --timeout
  • accepts standard Robot time formats (e.g., 30m, 1h) and supports runtime variables.
  • total_timeout is exposed as a property in _ExecutionContext, delegating to EXECUTION_CONTEXTS.total_timeout for global accessibility.
  • added _process_timeout in _BaseSettings to validate values immediately --> Invalid time strings or negative values trigger a failure before execution starts, ensuring fast feedback.
  • a new class TotalTimeout to manage the global timer. It ensures that when the limit is hit,
  • execution stops with messages: "Execution stopped: Total execution timeout of {value} exceeded."

acceptance tests : atest/robot/running/global_timeout.robot validates 16 distinct scenarios including nested suites, dry-run, variable expansion, and CLI validation.

Copy link
Copy Markdown
Member

@pekkaklarck pekkaklarck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feature touches many different parts of Robot and many of those parts are old and not that great code. Considering that, this PR is pretty impressive!

I didn't have time to go through the PR in detail, but I noticed some things that need to be changed. If you have questions, we can discuss this further on the #devel channel on Slack.

old_timeout.string = new_timeout_obj.string
# this way do NOT reset start_time, so it counts from original start.

print(f"DEBUG: Timeout after: {EXECUTION_CONTEXTS.total_timeout}")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EXECUTION_CONTEXT is not part of the public API and we have a plan to replaces this global variable with execution related context object in the future. EXECUTION_CONTEXT can be used in tests if needed, but it shouldn't be used in examples demonstrating how users could utilize it.

In general I don't consider the possibility increase the global timeout important and it should be part of this overall issue. Setting a maximum timeout for the whole execution is fine, but there are already other ways to stop the whole execution conditionally if needed.

if not value or value.upper() == "NONE":
return None
if contains_variable(value):
return value
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No other command line option supports variables and this option shouldn't support them either. Environment variables can be used like --timeout $TIMEOUT if something like this is needed.

def __init__(self):
self._contexts = []
self._asynchronous = Asynchronous()
self.total_timeout = None
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be _total_timeout to not encourage anyone to touch it. Such code will break at some point.


@property
def total_timeout(self):
return EXECUTION_CONTEXTS.total_timeout
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be _total_timeout or not exist at all to not encourage users to touch it.

self.suite = suite
self.test = None
self.timeouts = []
self.timeouts = [EXECUTION_CONTEXTS.total_timeout] if EXECUTION_CONTEXTS.total_timeout else []
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current execution context handling is a big mess that I hope to clean up at some point, but this code adds even more coupling between classes. A simple way to simplify this is adding global_timeout=None to __init__ signature and then doing something like

self.timeouts = [global_timeoutif global_timeout else []

here. If self.timeouts would need to be re-initialized later, global_timeout could be stored to self.global_timeout, but I hope we could avoid that.

def end_test(self, test):
self.test = None
self.timeouts = []
self.timeouts = [EXECUTION_CONTEXTS.total_timeout] if EXECUTION_CONTEXTS.total_timeout else []
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should be able to pop the possible test timeout from self.timeout here instead of re-initializing whole self.timeouts. Earlier doing self.timeouts = [] was so simple that it made sense, but popping ought to be simpler than re-initialization if global timeout exists.

timeout = self.settings["Timeout"]
if timeout:
return TotalTimeout(timeout, self.variables)
return None
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does SuiteRunner need total_timeout? Wouldn't it be enough if contexts has it?

with self.context.suite_teardown():
failure = self._run_teardown(suite, self.suite_status, self.suite_result)
if not failure and self.context.total_timeout and self.context.total_timeout.timed_out():
failure = ExecutionFailed(self.context.total_timeout.get_message(), test_timeout=True)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test_timeoout=True isn't exactly correct in this case. Not sure how much it matters.



class TotalTimeout(Timeout):
kind = "TOTAL EXECUTION"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed on Slack, this should be GlobalTimeout instead of TotalTimeout and that terminology should be used consistently. The kind attribute should probably have value GLOBAL.

from robot.output import LOGGER
msg = f"Execution stopped: Total execution timeout of {self.string} exceeded."
LOGGER.error(msg)
self._reported = True
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this situation reported separately as an error?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants