Skip to content

Commit d1f5b40

Browse files
authored
Retry mechanism for assertion keywords 'Check Row Count' and 'Check Query Result' (#216)
* Retry mechanism for assertion keywords 'Check Row Count' and 'Check Query Result' (fix #209) * Log message if timeout reached
1 parent 27d6bfb commit d1f5b40

5 files changed

Lines changed: 133 additions & 17 deletions

File tree

src/DatabaseLibrary/__init__.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,27 @@ class DatabaseLibrary(ConnectionManager, Query, Assertion):
9696
= Inline assertions =
9797
Keywords that accept arguments ``assertion_operator`` <`AssertionOperator`> and ``expected_value``
9898
perform a check according to the specified condition - using the [https://github.com/MarketSquare/AssertionEngine|Assertion Engine].
99-
| Check Row Count SELECT id FROM person == 2
100-
| Check Query Result SELECT first_name FROM person contains Allan
99+
100+
Examples:
101+
| Check Row Count | SELECT id FROM person | *==* | 2 |
102+
| Check Query Result | SELECT first_name FROM person | *contains* | Allan |
103+
104+
= Retry mechanism =
105+
Assertion keywords that accept arguments ``retry_timeout`` and ``retry_pause`` support waiting for assertion to pass.
106+
107+
Setting the ``retry_timeout`` argument enables the mechanism -
108+
in this case the SQL request and the assertion are executed in a loop,
109+
until the assertion is passed or the ``retry_timeout`` is reached.
110+
The pause between the loop iterations is set using the ``retry_pause`` argument.
111+
112+
The argument values are set in [http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#time-format|Robot Framework time format] -
113+
e.g. ``5 seconds``.
114+
115+
The retry mechanism is disabled by default - the ``retry_timeout`` is set to ``0``.
116+
117+
Examples:
118+
| Check Row Count | SELECT id FROM person | *==* | 2 | retry_timeout=10 seconds |
119+
| Check Query Result | SELECT first_name FROM person | *contains* | Allan | retry_timeout=5s | retry_timeout=1s |
101120
102121
= Database modules compatibility =
103122
The library is basically compatible with any [https://peps.python.org/pep-0249|Python Database API Specification 2.0] module.

src/DatabaseLibrary/assertion.py

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
from assertionengine import AssertionOperator, verify_assertion
1717
from robot.api import logger
18+
from robot.libraries.BuiltIn import BuiltIn
19+
from robot.utils import timestr_to_secs
1820

1921

2022
class Assertion:
@@ -275,6 +277,8 @@ def check_row_count(
275277
sansTran: bool = False,
276278
alias: Optional[str] = None,
277279
parameters: Optional[Tuple] = None,
280+
retry_timeout="0 seconds",
281+
retry_pause="0.5 seconds",
278282
):
279283
"""
280284
Check the number of rows returned from ``selectStatement`` using ``assertion_operator``
@@ -290,6 +294,9 @@ def check_row_count(
290294
Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different
291295
depending on the database client).
292296
297+
Use ``retry_timeout`` and ``retry_pause`` parameters to enable waiting for assertion to pass.
298+
See `Retry mechanism` for more details.
299+
293300
Examples:
294301
| Check Row Count | SELECT id FROM person WHERE first_name = 'John' | *==* | 1 |
295302
| Check Row Count | SELECT id FROM person WHERE first_name = 'John' | *>=* | 2 | assertion_message=my error message |
@@ -299,8 +306,19 @@ def check_row_count(
299306
| Check Row Count | SELECT id FROM person WHERE first_name = %s | *equals* | 5 | parameters=${parameters} |
300307
"""
301308
logger.info(f"Executing : Check Row Count | {selectStatement} | {assertion_operator} | {expected_value}")
302-
num_rows = self.row_count(selectStatement, sansTran, alias=alias, parameters=parameters)
303-
return verify_assertion(num_rows, assertion_operator, expected_value, "Wrong row count:", assertion_message)
309+
check_ok = False
310+
time_counter = 0
311+
while not check_ok:
312+
try:
313+
num_rows = self.row_count(selectStatement, sansTran, alias=alias, parameters=parameters)
314+
verify_assertion(num_rows, assertion_operator, expected_value, "Wrong row count:", assertion_message)
315+
check_ok = True
316+
except AssertionError as e:
317+
if time_counter >= timestr_to_secs(retry_timeout):
318+
logger.info(f"Timeout '{retry_timeout}' reached")
319+
raise e
320+
BuiltIn().sleep(retry_pause)
321+
time_counter += timestr_to_secs(retry_pause)
304322

305323
def check_query_result(
306324
self,
@@ -313,6 +331,8 @@ def check_query_result(
313331
sansTran: bool = False,
314332
alias: Optional[str] = None,
315333
parameters: Optional[Tuple] = None,
334+
retry_timeout="0 seconds",
335+
retry_pause="0.5 seconds",
316336
):
317337
"""
318338
Check value in query result returned from ``selectStatement`` using ``assertion_operator`` and ``expected_value``.
@@ -333,6 +353,9 @@ def check_query_result(
333353
Use optional ``parameters`` for query variable substitution (variable substitution syntax may be different
334354
depending on the database client).
335355
356+
Use ``retry_timeout`` and ``retry_pause`` parameters to enable waiting for assertion to pass.
357+
See `Retry mechanism` for more details.
358+
336359
Examples:
337360
| Check Query Result | SELECT first_name FROM person | *contains* | Allan |
338361
| Check Query Result | SELECT first_name, last_name FROM person | *==* | Schneider | row=1 | col=1 |
@@ -347,19 +370,31 @@ def check_query_result(
347370
logger.info(
348371
f"Executing : Check Query Results | {selectStatement} | {assertion_operator} | {expected_value} | row = {row} | col = {col} "
349372
)
350-
query_results = self.query(selectStatement, sansTran, alias=alias, parameters=parameters)
351-
352-
row_count = len(query_results)
353-
assert row < row_count, f"Checking row '{row}' is not possible, as query results contain {row_count} rows only!"
354-
col_count = len(query_results[row])
355-
assert (
356-
col < col_count
357-
), f"Checking column '{col}' is not possible, as query results contain {col_count} columns only!"
358373

359-
actual_value = query_results[row][col]
360-
return verify_assertion(
361-
actual_value, assertion_operator, expected_value, "Wrong query result:", assertion_message
362-
)
374+
check_ok = False
375+
time_counter = 0
376+
while not check_ok:
377+
try:
378+
query_results = self.query(selectStatement, sansTran, alias=alias, parameters=parameters)
379+
row_count = len(query_results)
380+
assert (
381+
row < row_count
382+
), f"Checking row '{row}' is not possible, as query results contain {row_count} rows only!"
383+
col_count = len(query_results[row])
384+
assert (
385+
col < col_count
386+
), f"Checking column '{col}' is not possible, as query results contain {col_count} columns only!"
387+
actual_value = query_results[row][col]
388+
verify_assertion(
389+
actual_value, assertion_operator, expected_value, "Wrong query result:", assertion_message
390+
)
391+
check_ok = True
392+
except AssertionError as e:
393+
if time_counter >= timestr_to_secs(retry_timeout):
394+
logger.info(f"Timeout '{retry_timeout}' reached")
395+
raise e
396+
BuiltIn().sleep(retry_pause)
397+
time_counter += timestr_to_secs(retry_pause)
363398

364399
def table_must_exist(
365400
self, tableName: str, sansTran: bool = False, msg: Optional[str] = None, alias: Optional[str] = None

test/resources/common.resource

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Documentation Global variables, which are used in all test common tests
55
Library Collections
66
Library OperatingSystem
77
Library DatabaseLibrary
8+
Library DateTime
89

910

1011
*** Variables ***
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
*** Settings ***
2+
Documentation Tests for assertion keywords with retry mechanism
3+
4+
Resource ../../resources/common.resource
5+
6+
Suite Setup Connect To DB And Prepare Data
7+
Suite Teardown Delete Data And Disconnect
8+
Test Setup Save Start Time
9+
10+
*** Variables ***
11+
${Timeout} ${3}
12+
${Tolerance} ${0.5}
13+
${Request} SELECT first_name FROM person
14+
15+
*** Test Cases ***
16+
17+
18+
Check Query Results With Timeout - Fast If DB Ready
19+
Check Query Result ${Request} contains Allan retry_timeout=${Timeout} seconds
20+
${End time}= Get Current Date
21+
${Execution time}= Subtract Date From Date ${End time} ${START_TIME}
22+
Should Be True 0 <= $Execution_time <= $Tolerance
23+
24+
Check Query Results With Timeout - Slow If Result Wrong
25+
Run Keyword And Expect Error Wrong query result: 'Franz Allan' (str) should contain 'Blah' (str)
26+
... Check Query Result ${Request} contains Blah retry_timeout=${Timeout} seconds retry_pause=1s
27+
${End time}= Get Current Date
28+
${Execution time}= Subtract Date From Date ${End time} ${START_TIME}
29+
Should Be True $Timeout <= $Execution_time <= $Timeout + $Tolerance
30+
31+
Check Query Results With Timeout - Slow If Row Count Wrong
32+
Run Keyword And Expect Error Checking row '5' is not possible, as query results contain 2 rows only!
33+
... Check Query Result ${Request} contains Blah row=5 retry_timeout=${Timeout} seconds
34+
${End time}= Get Current Date
35+
${Execution time}= Subtract Date From Date ${End time} ${START_TIME}
36+
Should Be True $Timeout <= $Execution_time <= $Timeout + $Tolerance
37+
38+
Check Row Count With Timeout - Fast If DB Ready
39+
Check Row Count ${Request} == 2 retry_timeout=${Timeout} seconds
40+
${End time}= Get Current Date
41+
${Execution time}= Subtract Date From Date ${End time} ${START_TIME}
42+
Should Be True 0 <= $Execution_time <= $Tolerance
43+
44+
Check Row Count With Timeout - Slow If Result Wrong
45+
Run Keyword And Expect Error Wrong row count: '2' (int) should be greater than '5' (int)
46+
... Check Row Count ${Request} > 5 retry_timeout=${Timeout} seconds retry_pause=1s
47+
${End time}= Get Current Date
48+
${Execution time}= Subtract Date From Date ${End time} ${START_TIME}
49+
Should Be True $Timeout <= $Execution_time <= $Timeout + $Tolerance
50+
51+
*** Keywords ***
52+
Connect To DB And Prepare Data
53+
Connect To DB
54+
Create Person Table And Insert Data
55+
56+
Delete Data And Disconnect
57+
Drop Tables Person And Foobar
58+
Disconnect From Database
59+
60+
Save Start Time
61+
${START_TIME}= Get Current Date
62+
Set Suite Variable ${START_TIME}

test/tests/common_tests/basic_tests.robot

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ Check Query Result With Assertion Engine - Col Out Of Range
8080
Run Keyword And Expect Error Checking column '5' is not possible, as query results contain 2 columns only!
8181
... Check Query Result SELECT id, first_name FROM person == Blah col=5
8282

83-
8483
Retrieve records from person table
8584
${output}= Execute SQL String SELECT * FROM person
8685
Log ${output}

0 commit comments

Comments
 (0)