Skip to content

Commit 07347ec

Browse files
authored
Cancel async keywords if execution is stopped (#4835)
Fixes #4808.
1 parent a5a2f17 commit 07347ec

File tree

5 files changed

+70
-2
lines changed

5 files changed

+70
-2
lines changed

atest/robot/running/stopping_with_signal.robot

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,32 @@ Skip Teardowns After Stopping Gracefully
6969
Teardown Should Not Be Defined ${tc}
7070
Teardown Should Not Be Defined ${SUITE}
7171

72+
SIGINT Signal Should Stop Async Test Execution Gracefully
73+
Start And Send Signal async_stop.robot One SIGINT 5
74+
Check Test Cases Have Failed Correctly
75+
${tc} = Get Test Case Test
76+
Evaluate len(${tc.kws[1].msgs}) == 1
77+
Check Log Message ${tc.kws[1].msgs[0]} Start Sleep
78+
Evaluate len(${SUITE.teardown.msgs}) == 0
79+
80+
Two SIGINT Signals Should Stop Async Test Execution Forcefully
81+
Start And Send Signal async_stop.robot Two SIGINTs 5
82+
Check Tests Have Been Forced To Shutdown
83+
84+
SIGTERM Signal Should Stop Async Test Execution Gracefully
85+
[Tags] no-windows
86+
Start And Send Signal async_stop.robot One SIGTERM 5
87+
Check Test Cases Have Failed Correctly
88+
${tc} = Get Test Case Test
89+
Evaluate len(${tc.kws[1].msgs}) == 1
90+
Check Log Message ${tc.kws[1].msgs[0]} Start Sleep
91+
Evaluate len(${SUITE.teardown.msgs}) == 0
92+
93+
Two SIGTERM Signals Should Stop Async Test Execution Forcefully
94+
[Tags] no-windows
95+
Start And Send Signal async_stop.robot Two SIGTERMs 5
96+
Check Tests Have Been Forced To Shutdown
97+
7298
*** Keywords ***
7399
Start And Send Signal
74100
[Arguments] ${datasource} ${signals} ${sleep}=0s @{extra options}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import asyncio
2+
3+
from robot.api import logger
4+
5+
6+
class AsyncStop:
7+
8+
async def async_test(self):
9+
logger.info("Start Sleep", also_console=True)
10+
await asyncio.sleep(2)
11+
logger.info("End Sleep", also_console=True)
12+
13+
async def async_sleep(self, time: int):
14+
await asyncio.sleep(time)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
*** Settings ***
2+
Suite Teardown Async Sleep ${TEARDOWN SLEEP}
3+
Library OperatingSystem
4+
Library Library.py
5+
Library AsyncStop.py
6+
7+
*** Test Case ***
8+
Test
9+
[Documentation] FAIL Execution terminated by signal
10+
Create File ${TESTSIGNALFILE}
11+
Async Test
12+
Fail Should not be executed
13+
14+
Test 2
15+
[Documentation] FAIL Test execution stopped due to a fatal error.
16+
Fail Should not be executed

doc/userguide/src/ExtendingRobotFramework/CreatingTestLibraries.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2067,6 +2067,10 @@ More examples of functionality:
20672067
manage the task and save a reference to avoid it being garbage collected. If the event loop
20682068
closes and a task is still pending, a message will be printed to the console.
20692069

2070+
.. note:: If execution of keyword cannot continue for some reason, for example a signal stop,
2071+
Robot Framework will cancel the async task and any of its children. Other async tasks will
2072+
continue running normally.
2073+
20702074
Communicating with Robot Framework
20712075
----------------------------------
20722076

src/robot/running/context.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import asyncio
1818
from contextlib import contextmanager
1919

20-
from robot.errors import DataError
20+
from robot.errors import DataError, ExecutionFailed
2121

2222

2323
class Asynchronous:
@@ -36,7 +36,15 @@ def close_loop(self):
3636
self._loop_ref.close()
3737

3838
def run_until_complete(self, coroutine):
39-
return self.event_loop.run_until_complete(coroutine)
39+
task = self.event_loop.create_task(coroutine)
40+
try:
41+
return self.event_loop.run_until_complete(task)
42+
except ExecutionFailed as e:
43+
if e.dont_continue:
44+
task.cancel()
45+
# wait for task and its children to cancel
46+
self.event_loop.run_until_complete(asyncio.gather(task, return_exceptions=True))
47+
raise e
4048

4149
def is_loop_required(self, obj):
4250
return inspect.iscoroutine(obj) and not self._is_loop_running()

0 commit comments

Comments
 (0)